Type Coercion: ToPrimitive, ToNumber, ToBoolean
[] + {} = '[object Object]' — magic nahi, spec hai. Step by step algorithm chalao bhai.
When JavaScript encounters an object where it expects a primitive — during +, -, comparisons, or template literals — it runs an internal algorithm called ToPrimitive. This algorithm decides how to convert any object into a primitive value.
ToPrimitive takes a hint: 'number', 'string', or 'default' (which behaves like 'number' for most built-in objects). The hint determines the ORDER of method calls:
number hint (arithmetic, <, >): call valueOf() first → if primitive, use it → else call toString() → if primitive, use it → else TypeError
string hint (template literals, String()): call toString() first → if primitive, use it → else call valueOf() → if primitive, use it → else TypeError
const obj = {
valueOf() { return 42; },
toString() { return 'hello'; }
};
// 'number' hint: valueOf() first
console.log(obj + 0); // 42 — valueOf() → 42
console.log(obj - 0); // 42 — arithmetic: number hint
console.log(obj * 2); // 84 — arithmetic: number hint
// 'string' hint: toString() first
console.log(`${obj}`); // 'hello' — template: string hint
console.log(String(obj)); // 'hello' — String(): string hint
// 'default' hint (behaves like number for most objects)
console.log(obj == 42); // true — default hint → valueOf → 42
// If valueOf returns non-primitive:
const obj2 = {
valueOf() { return {}; }, // returns object — not a primitive
toString() { return 'text'; }
};
console.log(obj2 + ''); // 'text' — valueOf fails, falls to toString
ToPrimitive_Number and ToPrimitive_String. Every + operation that involves an object goes through this — it's called before any arithmetic or concatenation. Symbol.toPrimitive bypasses the valueOf/toString lookup entirely.ToNumber is called whenever a value must become a number: arithmetic -, *, /, unary +, and Number(). The rules are explicit in the ECMAScript spec:
// ToNumber rules:
console.log(Number(undefined)); // NaN
console.log(Number(null)); // 0
console.log(Number(true)); // 1
console.log(Number(false)); // 0
console.log(Number('')); // 0 — empty string!
console.log(Number('42')); // 42
console.log(Number(' 42 ')); // 42 — whitespace trimmed!
console.log(Number('0x1F')); // 31 — hex!
console.log(Number('0b1010')); // 10 — binary!
console.log(Number('hello')); // NaN
console.log(Number('42abc')); // NaN — not a valid number
// Objects go through ToPrimitive first:
console.log(Number([])); // 0 — [] → '' → 0
console.log(Number([42])); // 42 — [42] → '42' → 42
console.log(Number([1, 2])); // NaN — [1,2] → '1,2' → NaN
console.log(Number({})); // NaN — {} → '[object Object]' → NaN
// Unary + is shorthand for Number():
console.log(+'42'); // 42
console.log(+true); // 1
console.log(+null); // 0
console.log(+undefined); // NaN
console.log(+[]); // 0
console.log(+{}); // NaN
Number.isFinite(Number(x)) AND check for empty string separately, or parse with parseInt/parseFloat.ToBoolean is the simplest coercion algorithm: only 8 values are falsy, EVERYTHING else is truthy. No exceptions, no special cases, no hints.
The 8 falsy values — memorize these:
console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(0n)); // false — BigInt zero
console.log(Boolean('')); // false — empty string only!
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false
// EVERYTHING else is truthy — including these surprises:
console.log(Boolean('0')); // true — non-empty string!
console.log(Boolean('false')); // true — non-empty string!
console.log(Boolean([])); // true — empty array!
console.log(Boolean({})); // true — empty object!
console.log(Boolean(function(){})); // true — function!
console.log(Boolean(Infinity)); // true — infinity!
console.log(Boolean(-Infinity)); // true — negative infinity!
console.log(Boolean(new Boolean(false))); // true — object wrapper!
// !! as ToBoolean cast:
console.log(!!0); // false
console.log(!!''); // false
console.log(!!'hello'); // true
console.log(!!null); // false
console.log(!![]); // true — empty array is truthy!
if(x) into a ToBoolean check on x — for SMIs (Small Integers), it's just checking if the value is zero. For HeapObjects, it checks the instance type against the falsy singletons.The + operator is BOTH addition AND string concatenation. The spec decides which based on operand types — and this makes it coercion's most complex case.
Algorithm: ToPrimitive('default') on both operands → if EITHER result is a string → concatenate → else → add as numbers
// [] + [] :
// ToPrimitive([]) → '' (array toString = join, empty = '')
// Both primitives: '' is string → concatenate
console.log([] + []); // '' (empty string)
// [] + {} :
// ToPrimitive([]) → ''
// ToPrimitive({}) → '[object Object]'
// Either is string → concatenate
console.log([] + {}); // '[object Object]'
// {} + [] — STATEMENT POSITION:
// {} is parsed as EMPTY BLOCK (not object literal!)
// then +[] is unary plus: +'' = 0
// To make {} an object: wrap in parentheses
console.log(({}) + []); // '[object Object]'
// Other coercion surprises:
console.log(1 + '2'); // '12' — number coerced to string
console.log('3' - 1); // 2 — string coerced to number (- not ambiguous)
console.log('3' * '2'); // 6 — both to number
console.log(true + true); // 2 — both to number (1+1)
console.log(true + '1'); // 'true1' — string concat
console.log(null + 1); // 1 — null → 0
console.log(undefined + 1); // NaN — undefined → NaN
console.log([] + 1); // '1' — [] → '' → string concat!
{} + [] = 0 vs ({}) + [] = '[object Object]' distinction is pure JavaScript parser behavior. When {} appears at the start of a statement, the parser assumes it's a block (like an if body). When it appears as an expression (after (, return, =, etc.) it's an object literal. This context-sensitivity is one of JS's most notorious parsing quirks.The == operator (Abstract Equality Comparison) has 10+ rules in the ECMAScript spec. It's the most complex coercion algorithm — and the most dangerous.
Key rules (simplified):
// null/undefined special case — only equal to each other
console.log(null == undefined); // true
console.log(null == 0); // false — null only equals null/undefined
console.log(null == ''); // false
console.log(null == false); // false
// number vs string: string → number
console.log(42 == '42'); // true — '42' → 42
console.log(42 == '42.0'); // true — '42.0' → 42
console.log(0 == ''); // true — '' → 0
console.log(0 == '0'); // true — '0' → 0
console.log('' == '0'); // false — same type, different value!
// boolean: boolean → number FIRST
console.log(true == 1); // true — true → 1
console.log(false == 0); // true — false → 0
console.log(true == '1'); // true — true→1, '1'→1
console.log(false == ''); // true — false→0, ''→0
// The infamous [] == false:
// Step 1: boolean == object → convert boolean: false → 0
// Step 2: number == object → ToPrimitive([]) → ''
// Step 3: number == string → Number('') → 0
// Step 4: 0 == 0 → true!
console.log([] == false); // true — four-step coercion!
console.log([] == 0); // true
console.log([] == ''); // true
console.log([1] == 1); // true — [1] → '1' → 1
null == undefined (and nothing else), then forget everything else and use ===. ESLint's eqeqeq rule enforces this in production code. The one useful case for == in real code: x == null to check for null OR undefined in one shot.Lo kar liya — Key Points:
- ✅ ToPrimitive('number'): valueOf() first, then toString(). ToPrimitive('string'): toString() first, then valueOf()
- ✅ ToNumber rules: null→0, undefined→NaN, ''→0, '42'→42, []→0, {}→NaN
- ✅ ToBoolean: only 8 values are falsy. Everything else is truthy — including '0', [], {}, Infinity
- ✅ + coerces with ToPrimitive('default') — if either result is string, concatenates. Otherwise adds numbers
- ✅ {} + [] is 0 (statement position). ({}) + [] is '[object Object]' (expression position)
- ✅ Loose == is a 10-step algorithm — avoid it everywhere except x == null (checks null AND undefined)
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login