Home
🔢
Segment 1 — Core Types & Coercion
The foundation of all JavaScript. Every bug you can't explain usually comes from not knowing how types behave. This segment covers primitives, reference types, type checking, coercion rules, and the infamous == algorithm — the things every JS interview starts with.
Primitives Type Coercion Type Checking Reference Types Symbol Abstract Equality
10
Questions
0
Opened
4
Topics
~45m
Study Time
🧠 Foundation — How JavaScript Stores Values

Before touching type-checking APIs or coercion rules, you need one mental model: where does JavaScript store a value, and what gets copied when you assign it?

Primitives — stored by VALUE

The 8 primitives: string, number, bigint, boolean, undefined, null, symbol, object*.

When you copy a primitive, you get a completely independent copy. Changing one does NOT affect the other.

let a = 5;
let b = a;
b = 10;
// a is still 5 ✅
Objects — stored by REFERENCE

Arrays, objects, functions, Maps, Sets — all reference types. The variable holds a pointer to data on the heap, not the data itself.

When you copy an object variable, both variables point to the same object in memory.

let a = {x:1};
let b = a;
b.x = 99;
// a.x is now 99 ⚠
🏠 The House Address Analogy

A primitive is like writing a number on a sticky note. If you copy the sticky note, you get your own independent copy — changing yours doesn't affect the original.

An object is like writing a house address on a sticky note. If you copy the note, both copies point to the same house. Knocking down a wall in that house affects everyone holding that address.

StackFast, limited memory where primitives and variable references live. Automatically managed as function calls enter and exit.
HeapLarge, dynamic memory where objects live. Managed by the garbage collector — you don't free it manually.
Pass by valueThe function gets a copy of the primitive. Mutations inside the function don't affect the caller's variable.
Pass by referenceThe function gets the same memory address. Mutations to object properties inside the function ARE visible outside.
⚠ The "Pass by Reference" Misconception
JavaScript passes object references by value — it copies the address, not the object. If you reassign the parameter (param = {}), the caller's variable is unaffected. Only mutations (param.x = 1) are shared.
Topic A — Primitives & Coercion
1
What are JavaScript's primitive types? How are they different from objects?
Easy Primitives
TypeExample valuestypeof resultNotes
string"hello", '', `template`"string"Immutable — no method can change the original string
number42, 3.14, NaN, Infinity"number"64-bit IEEE 754 float. NaN is a number type!
bigint9007199254740991n"bigint"Integers beyond Number.MAX_SAFE_INTEGER
booleantrue, false"boolean"Only two values
undefinedundefined"undefined"Variable declared but not assigned
nullnull"object"Historical bug — null is a primitive, not an object
symbolSymbol('id')"symbol"Unique, non-enumerable identifiers (ES6)
object{}, [], function(){}"object" / "function"Reference type — everything that isn't a primitive
JavaScript
// ── PRIMITIVES are immutable and compared by VALUE ──
let a = "hello";
let b = a;
b = "world";
console.log(a); // "hello" — unchanged

// String methods return NEW strings — they never mutate
let s = "hello";
s.toUpperCase(); // returns "HELLO" — s is still "hello"
s[0] = "H";        // silently fails in non-strict mode

// ── OBJECTS are mutable and compared by REFERENCE ──
let obj1 = { x: 1 };
let obj2 = obj1;          // copies the reference (address)
obj2.x = 99;
console.log(obj1.x);      // 99 — same object!

// ── typeof quirks to memorise ──
typeof null       // "object"  ← famous historical bug
typeof []         // "object"  ← arrays are objects
typeof function(){} // "function" ← special case for functions
typeof undefined  // "undefined"
typeof Symbol()   // "symbol"

Primitives are not objects, yet you can call "hello".toUpperCase(). How? JavaScript temporarily auto-boxes the primitive into its wrapper object (String, Number, Boolean), calls the method, then discards the wrapper.

JavaScript
// What JS does internally when you call a string method:
"hello".toUpperCase();
// → new String("hello").toUpperCase()  → discards wrapper → "HELLO"

// Never use wrapper object constructors — they create OBJECTS, not primitives:
typeof new String("hi") // "object" ← not "string"!
new Boolean(false) == true // true — because objects are always truthy ⚠
How to answer in an interview

"JavaScript has 7 primitive types — string, number, bigint, boolean, undefined, null, and symbol. Primitives are immutable and compared by value. Objects (arrays, functions, plain objects) are reference types — variables hold a pointer to the data in the heap. One gotcha: typeof null === 'object' is a historical bug — null is a primitive. Another: primitives can call methods because JS temporarily boxes them in wrapper objects."

2
What is type coercion? Explain implicit vs explicit coercion with examples.
Medium Type Coercion
🎯 Mental Model — Coercion as Translation

Type coercion is JavaScript automatically translating a value from one type to another when an operation requires it. Think of it as a translator that makes guesses when you haven't been explicit — sometimes the guess is right, sometimes it's spectacularly wrong.

🌍 The Translator Analogy

Explicit coercion = hiring a professional translator you trust (Number("42"), String(99)). You control exactly what happens.

Implicit coercion = JavaScript guessing what you meant. Usually fine, but the rules are non-obvious — which is why this topic comes up in every interview.

JavaScript
// To Number
Number("42")        // 42
Number("")          // 0  ← surprising!
Number(true)       // 1
Number(false)      // 0
Number(null)       // 0  ← surprising!
Number(undefined)  // NaN
Number("abc")      // NaN
Number([])         // 0  ← very surprising!
Number([1])        // 1
Number([1,2])      // NaN
parseInt("42px")  // 42  (stops at first non-digit)
+"42"              // 42  (unary + operator)

// To String
String(42)         // "42"
String(null)       // "null"
String(undefined)  // "undefined"
(42).toString()    // "42"

// To Boolean
Boolean(0)         // false
Boolean("")        // false
Boolean(null)      // false
Boolean({})        // true  ← empty object is truthy!
!!value            // double-negation trick — most common
JavaScript — The + operator is the trickiest
// + prefers STRING concatenation if either operand is a string
"5" + 3        // "53"   (number coerced to string)
5   + "3"      // "53"
"5" + true    // "5true"
"5" + null    // "5null"
[] + []        // ""     (both convert to "")
[] + {}        // "[object Object]"
{} + []        // 0  ← {} parsed as empty BLOCK, not object!

// - * / prefer NUMBER coercion
"5" - 3        // 2     (string coerced to number)
"5" * "3"      // 15
"abc" - 1     // NaN

// Comparison operators — many gotchas
1 < "2"        // true  (string → number)
"a" > "B"     // true  (char codes: a=97, B=66)
null > 0      // false
null == 0     // false  ← null only == undefined
null >= 0     // true   ← bizarre! (null converts to 0 for >=)
The Rule of Thumb
Use === for all equality checks to skip coercion entirely. Use explicit conversion functions (Number(), String(), Boolean()) instead of relying on implicit coercion — your future self will thank you.
Interview Answer

"Type coercion is JavaScript automatically converting values between types. Explicit coercion is deliberate — Number('42'), String(99). Implicit coercion is triggered by operators — + prefers string concatenation if either side is a string, while -, *, / prefer numbers. The rule I follow: always use === to avoid the coercion rabbit hole."

3
What are all the falsy values in JavaScript? What are the surprising truthy gotchas?
Easy Type Coercion
ValueTypeWhy falsy?
falsebooleanLiterally false
0numberZero — numeric nothing
-0numberNegative zero — same as 0 in boolean context
0nbigintBigInt zero
""stringEmpty string — absence of characters
nullnullIntentional absence of a value
undefinedundefinedVariable not assigned
NaNnumberNot a valid number

Everything else is truthy — including empty objects {}, empty arrays [], and the string "false".

JavaScript — Common Interview Traps
// ⚠ These are ALL truthy — they catch people out
if ({})          console.log('truthy'); // ✅ empty object
if ([])          console.log('truthy'); // ✅ empty array
if ("false")     console.log('truthy'); // ✅ non-empty string
if ("0")         console.log('truthy'); // ✅ "0" is truthy but Number("0") is 0 (falsy)
if (new Boolean(false)) console.log('truthy'); // ✅ OBJECTS are always truthy
if (function(){}) console.log('truthy'); // ✅ functions are objects

// Practical consequence — how to check for empty array:
if (arr)                  // ❌ ALWAYS truthy — even if empty
if (arr.length)           // ✅ 0 is falsy, non-zero is truthy
if (arr.length > 0)       // ✅ most explicit

// Practical consequence — the "0" string bug:
const input = "0";   // user typed "0"
if (input) { /* runs — "0" is truthy ✅ */ }
if (Number(input)) { /* does NOT run — Number("0") is 0, falsy */ }
Memory Trick
Memorise the 8 falsy values as "zero things and nothing": false, 0, -0, 0n (the zeros), "" (empty string), null, undefined, NaN. Every other value — no matter how "empty" looking — is truthy.
Topic B — Type Checking & Edge Cases
4
How do you reliably check types in JavaScript? Compare typeof, instanceof, and Object.prototype.toString.call().
Medium Type Checking
MethodBest forLimitations
typeof xPrimitives (string, number, boolean, undefined, symbol, bigint, function)typeof null === "object"; can't distinguish array from object
x instanceof CChecking if object was created by a specific constructor / classFails across iframes (different realms); doesn't work on primitives
Object.prototype.toString.call(x)Precise type tag for any value including null, arrays, RegExp, DateVerbose; can be spoofed via Symbol.toStringTag
JavaScript
// ── typeof ── works perfectly for primitives
typeof "hello"     // "string"
typeof 42          // "number"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof Symbol()    // "symbol"
typeof 42n         // "bigint"
typeof function(){} // "function" ← special case
typeof {}           // "object"
typeof []           // "object" ← can't distinguish array!
typeof null         // "object" ← the famous bug

// Safe null check using typeof:
if (x !== null && typeof x === "object") { /* it's a real object */ }

// ── instanceof ── checks the prototype chain
[] instanceof Array   // true
[] instanceof Object  // true  ← arrays ARE objects
new Date() instanceof Date  // true
"hello" instanceof String  // false! ← primitive, not wrapper object

// ── Object.prototype.toString ── most precise
const typeOf = x => Object.prototype.toString.call(x).slice(8, -1);

typeOf(null)        // "Null"
typeOf(undefined)   // "Undefined"
typeOf([])          // "Array"   ← correctly identifies arrays
typeOf({})          // "Object"
typeOf(new Date())  // "Date"
typeOf(/regex/)     // "RegExp"
typeOf(Promise.resolve()) // "Promise"

// ── Practical helpers for daily use ──
const isArray    = x => Array.isArray(x);         // best for arrays
const isNull     = x => x === null;              // only way
const isObject   = x => typeof x === 'object' && x !== null;
⚠ The Cross-Realm instanceof Problem
If an array is created in an iframe, iframeArray instanceof Array returns false because each browsing context has its own Array constructor. Array.isArray() works across realms — always prefer it for array checks.
5
What is the difference between null, undefined, and undeclared? When does each appear?
Easy Type Checking
nullundefinedundeclared
MeaningIntentional absence of a value — explicitly set by the developerVariable declared but no value assigned yet — JS's default emptyVariable was never declared at all — doesn't exist in scope
typeof"object" (bug)"undefined""undefined" — no error! (safety valve)
== nulltruetrueReferenceError if accessed
Common causeAPI returning no result, resetting a refMissing args, uninitialised vars, missing object propsTypos, missing imports, not-yet-declared vars
JavaScript
// null — intentional emptiness
let selectedUser = null;          // nothing selected yet
function findUser(id) {
  const user = db.find(id);
  return user ?? null;             // explicit: "found nothing"
}

// undefined — appears automatically
let x;                             // undefined — declared, not assigned
function greet(name) {
  console.log(name);               // undefined if called with no arg
}
const obj = {};
obj.missing;                       // undefined — property doesn't exist
[1,2,3][99];                        // undefined — out of bounds index

// undeclared — accessing causes ReferenceError
console.log(notDeclared);          // ❌ ReferenceError
typeof notDeclared;               // "undefined" — safe, no error

// Safe pattern to check for undeclared globals:
if (typeof myGlobal !== 'undefined') { /* safe */ }

// Null check — the nullish pattern
if (value == null) { /* catches BOTH null AND undefined */ }
if (value === null) { /* only null */ }
if (value === undefined) { /* only undefined */ }
Convention: null vs undefined
A common team convention: null = "I intentionally set this to empty". undefined = "JS set this because nothing was provided". When you want to express "no value found", return null. Let JS handle undefined naturally — don't assign it manually.
6
Why is typeof NaN === 'number'? How do you safely detect NaN and -0?
Medium Type Checking

NaN (Not-a-Number) is the result of an invalid numeric operation. It's part of the IEEE 754 floating-point standard and is categorised as a number type — representing a numeric computation that has no meaningful result.

JavaScript
// Ways NaN appears
Number("abc")     // NaN — invalid conversion
0 / 0            // NaN
Math.sqrt(-1)    // NaN — no real square root of negative
parseInt("xyz") // NaN
undefined + 1   // NaN

// NaN's most bizarre property: it doesn't equal itself
NaN === NaN      // false  ← NaN is the only value not equal to itself
NaN !== NaN      // true

// ❌ Wrong ways to detect NaN:
value === NaN    // always false — even for NaN itself!

// ✅ Correct ways to detect NaN:
isNaN(value)              // true for NaN, BUT coerces first! isNaN("abc") → true
Number.isNaN(value)       // ✅ best: no coercion. Number.isNaN("abc") → false
value !== value           // true only for NaN (exploits the self-inequality)
JavaScript
// -0 exists and behaves strangely
const negZero = -0;

// Most operations treat -0 same as 0
-0 === 0              // true  ← === can't distinguish them!
-0 > 0               // false
String(-0)           // "0"   ← loses the sign
JSON.stringify(-0)   // "0"   ← loses the sign

// ✅ Ways to detect -0:
Object.is(-0, 0)       // false ← Object.is has no coercion, distinguishes -0
Object.is(-0, -0)      // true
1 / -0 === -Infinity  // true ← divide by -0 gives -Infinity

// Object.is — the "same value equality" algorithm
Object.is(NaN, NaN)   // true  ← NaN equals NaN here (unlike ===)
Object.is(-0, 0)       // false ← -0 ≠ +0 here (unlike ===)
// Otherwise identical to ===
Interview Answer

"NaN is typed as 'number' because it represents an invalid floating-point result per IEEE 754 — it's inside the number type domain. The key oddity is NaN !== NaN, which means you can't use === to check for it. Always use Number.isNaN(), not the older isNaN() which coerces first. For -0, use Object.is(value, -0) — the only reliable way since -0 === 0 is true."

Topic C — Reference Types, Equality & Copying
7
Why does {} === {} return false? How does reference equality work?
Easy Reference Types

Every time you write {}, JavaScript creates a new object at a new memory address. The === operator for objects doesn't compare their contents — it compares their memory addresses. Two different objects, even with identical contents, live at different addresses.

JavaScript
// {} === {} is like asking "is house at 12 Oak St === house at 14 Oak St"
{} === {}                        // false — different addresses
[] === []                        // false — same reason

// Equality only holds when pointing to THE SAME object
const a = { x: 1 };
const b = a;                    // b holds the same address as a
a === b                         // true — same reference

// Value comparison of objects — you must write your own or use a library
const deepEqual = (a, b) =>
  JSON.stringify(a) === JSON.stringify(b); // quick but has limits (undefined, functions)

// In tests: Jest's toEqual() does deep structural comparison
expect({ a: 1 }).toEqual({ a: 1 }); // ✅ passes
expect({ a: 1 }).toBe({ a: 1 });    // ❌ fails (reference check)

// React's useState — same reference = no re-render
const [items, setItems] = useState([]);
setItems(items);         // ❌ same reference — no re-render
setItems([...items]);   // ✅ new reference — triggers re-render
8
What is the difference between shallow copy and deep copy? What methods give you which?
Medium Reference Types
🎯 Mental Model — The Ice Cube Tray
🧊 Shallow vs Deep Copy

Shallow copy = copying the tray but reusing the same ice cubes. The top level is independent, but nested items are still shared — changing a nested object affects both copies.

Deep copy = making a completely new tray with completely new ice cubes. No shared references anywhere — fully independent.

JavaScript
const original = { a: 1, nested: { x: 10 } };

// Spread operator — shallow copy
const copy1 = { ...original };

// Object.assign — shallow copy
const copy2 = Object.assign({}, original);

// Both are SHALLOW — nested object is still shared
copy1.a = 99;            // ✅ independent — original.a is still 1
copy1.nested.x = 99;   // ⚠ original.nested.x also becomes 99!

// Array shallow copies
const arrCopy = [...original]; // spread
const arrCopy2 = arr.slice(); // slice with no args
const arrCopy3 = Array.from(arr);
MethodWorks forLimitations
structuredClone(obj)Objects, arrays, Date, Map, Set, RegExp, ArrayBufferCannot clone functions, DOM nodes, class instances with methods
JSON.parse(JSON.stringify(obj))Plain objects and arrays with JSON-compatible valuesLoses undefined, functions, Symbol, Date becomes string, circular ref crashes
lodash.cloneDeep(obj)Almost everything including class instancesExternal dependency
Custom recursive functionFull controlMust handle edge cases manually
JavaScript
const obj = {
  name: 'Alice',
  scores: [90, 85],
  date: new Date(),
  fn: () => 'hello'
};

// structuredClone — the modern standard (Node 17+, modern browsers)
const deep1 = structuredClone(obj);
deep1.scores.push(100); // original.scores unaffected ✅
// Note: obj.fn is NOT cloned — structuredClone ignores functions

// JSON.parse/stringify — quick but lossy
const deep2 = JSON.parse(JSON.stringify(obj));
// obj.date → string, obj.fn → gone, obj.undefined → gone
Modern Best Practice
Use structuredClone() as the default deep copy solution — it handles Date, Map, Set, ArrayBuffer correctly and it's native (no dependencies). Fall back to lodash.cloneDeep only when you need to clone class instances with methods.
Topic D — Symbol & the Abstract Equality Algorithm
9
What is Symbol? What problem does it solve? Show practical use cases.
Medium Symbol

A Symbol is a guaranteed-unique primitive value. Every call to Symbol() creates a new, distinct value that will never equal any other Symbol or any other value — even if created with the same description string.

This solves the key collision problem: adding properties to objects you don't own without risk of overwriting existing properties.

JavaScript
// ── Basic: every Symbol is unique ──
const s1 = Symbol('id');
const s2 = Symbol('id');
s1 === s2   // false — description is just a label, not the value

// ── Use case 1: unique object keys (avoid naming collisions) ──
const ID = Symbol('id');
const user = { [ID]: 123, name: 'Alice' };
user[ID]     // 123 — only accessible via the Symbol reference
user.id      // undefined — string "id" key doesn't exist

// Symbol keys are NOT enumerable in for...in or Object.keys()
Object.keys(user)   // ['name'] — Symbol key hidden
JSON.stringify(user) // '{"name":"Alice"}' — Symbol stripped
Object.getOwnPropertySymbols(user)  // [Symbol(id)]

// ── Use case 2: private-like class fields (pre-#fields era) ──
const _secret = Symbol('secret');
class Vault {
  constructor(val) { this[_secret] = val; }
  reveal() { return this[_secret]; }
}

// ── Use case 3: well-known Symbols — hook into JS internals ──
class Range {
  constructor(start, end) { this.start = start; this.end = end; }
  [Symbol.iterator]() {     // makes Range iterable!
    let cur = this.start;
    const end = this.end;
    return { next: () => cur <= end ? { value: cur++, done: false } : { done: true } };
  }
}
[...new Range(1,5)]  // [1, 2, 3, 4, 5]

// ── Symbol.for — global symbol registry (shared across files) ──
const g1 = Symbol.for('shared'); // creates or retrieves from registry
const g2 = Symbol.for('shared');
g1 === g2  // true — same registry entry
Interview Answer

"Symbol is a primitive that guarantees uniqueness — two Symbol() calls with the same description are never equal. The main use cases are: collision-free object keys (they don't appear in for...in or JSON.stringify), and well-known Symbols that hook into language internals like Symbol.iterator to make objects iterable, Symbol.toPrimitive to control coercion, or Symbol.hasInstance to customise instanceof."

10
Walk through the Abstract Equality Algorithm (==). Explain: [] == false, null == undefined, "0" == false.
Hard Abstract Equality
🎯 The == Algorithm — Decision Tree

When you write x == y, JavaScript follows a specific set of rules defined in the ECMAScript spec. Knowing these rules lets you predict any == result without memorising every case.

1
Same type? Use === rules directly (no coercion needed)
2
null == undefined?true. These two and only these two are equal via ==
3
number == string? → convert string to number: 1 == "1"1 == 1true
4
boolean involved? → convert boolean to number first: false→0, true→1
5
object == primitive? → convert object with ToPrimitive (calls .valueOf() then .toString())
6
null or undefined vs anything else? → always false (null only equals undefined)
JavaScript — Step-by-step traces
// ── Case 1: null == undefined ──
null == undefined   // true — spec rule: these two are always equal via ==
null == 0           // false — null only == undefined, nothing else
null == ""          // false
null == false       // false ← catches many people out

// ── Case 2: "0" == false ──
"0" == false
// Step 4: boolean involved → convert false to number → 0
// Now: "0" == 0
// Step 3: number vs string → convert "0" to number → 0
// Now: 0 == 0 → true ✅
// But: if ("0") → TRUE (non-empty string is truthy)
// Paradox: "0" is truthy, yet "0" == false is true!

// ── Case 3: [] == false ──
[] == false
// Step 4: boolean → 0 → [] == 0
// Step 5: object vs primitive → [].valueOf() → [] (not primitive)
//          → [].toString() → "" → "" == 0
// Step 3: string vs number → Number("") → 0 → 0 == 0 → true

// ── Case 4: [] == ![] ──  (famous interview trick)
[] == ![]
// ![] = !true = false (objects are truthy → ![] is false)
// Now: [] == false → true (same as Case 3)
// So: [] == ![] is TRUE! Both sides coerce to 0.

// ── Summary table for common == cases ──
"" == 0             // true  (""→0)
"1" == true         // true  (true→1, "1"→1)
"0" == false        // true  (false→0, "0"→0)
null == undefined   // true  (spec rule)
NaN == NaN           // false (NaN is never equal to anything)
🚫 The Professional Answer
The correct response to "when should I use == in production?" is: almost never. The only widely accepted use of == is the value == null check (catches both null and undefined in one comparison). For all other comparisons, use === and perform explicit conversions when needed.
How to answer in an interview

"The == algorithm coerces types before comparing. The key rules are: booleans are converted to numbers first (false→0, true→1), strings are converted to numbers when compared against numbers, and objects call ToPrimitive (valueOf, then toString) when compared to primitives. The special case is null == undefined which is always true by spec. The infamous [] == ![] is true because both sides end up as 0. In production, I use === everywhere except for the null-check shorthand x == null."