Type Conversion & Coercion
The three abstract conversions, + is special, The infamous outputs, explained, Truthy and falsy, Explicit conversion (do this instead of relying on coercion) 🟢
ELI12. Coercion is JavaScript quietly changing a value's type to make an operation "work." + with a string means "glue strings," so 1 + "2" becomes "12". - only makes sense for numbers, so "5" - 2 becomes 3. The rules are consistent once you know them — they just look chaotic.
The three abstract conversions#
- ToPrimitive(obj, hint) — objects convert by calling
Symbol.toPrimitive, thenvalueOf/toString(order depends on hint"number"/"string"/"default"). - ToNumber —
""→0," 12 "→12,"12x"→NaN,true→1,false/null→0,undefined→NaN,[]→0,[5]→5,[1,2]→NaN,{}→NaN. - ToString — arrays join with commas (
[1,2]→"1,2",[]→""), objects →"[object Object]",null→"null".
+ is special#
+ means addition only if both operands are numbers/bigints after ToPrimitive; otherwise it's string concatenation. Every other arithmetic operator forces ToNumber.
The infamous outputs, explained#
[] + [] // "" → []→"" , ""+"" = ""
[] + {} // "[object Object]" → ""+"[object Object]"
{} + [] // 0 at statement start ⚠️
// {} is an empty BLOCK, then +[] → ToNumber([]) → 0
// as an expression: ({}) + [] === "[object Object]"
"5" - 2 // 3 → "-" forces numbers: 5 - 2
"5" + 2 // "52" → "+" with a string concatenates
true + true // 2 → true→1, 1+1
null + 1 // 1 → null→0
undefined + 1 // NaN → undefined→NaN
NaN == NaN // false → NaN is never equal to anything, even itself
1 + "2" + 3 // "123" → left-to-right: "12" then "123"
1 + 2 + "3" // "33" → 3 then "33"
[] == ![] // true ⚠️ → ![] is false → [] == false → "" == 0 → 0 == 0
"" == 0 // true → "" → 0
[null] == "" // true → [null] → ""
The {} + [] result depends entirely on context (statement vs expression) — the #1 "gotcha" people misquote. 🎯
Truthy and falsy#
Only 8 falsy values exist, memorize them:
false, 0, -0, 0n, "", null, undefined, NaN
Everything else is truthy — including "0", "false", [], {}, and function(){}.
if ([]) console.log("runs — empty array is truthy");
if ({}) console.log("runs — empty object is truthy");
if ("0") console.log("runs — non-empty string is truthy");
Boolean("") // false
!!"hello" // true (double-bang → boolean)
Explicit conversion (do this instead of relying on coercion) 🟢#
Number("42") // 42 (NaN on failure)
parseInt("42px") // 42 (stops at non-digit; give a radix: parseInt("0x1",16))
parseFloat("3.14abc") // 3.14
String(42) // "42"
(42).toString(2) // "101010" (binary)
Boolean(x) / !!x
Array.from("abc") // ["a","b","c"]
JSON.parse / JSON.stringify // structured
Interview Q&A#
Q1. List all falsy values.
Exactly 8: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including "0", "false", [], and {}.
Q2. Explain [] + {} vs {} + [].
[] + {} is "" + "[object Object]" = "[object Object]". {} + [] at statement start parses {} as an empty block, leaving +[] which is ToNumber([]) = 0. As an expression, ({}) + [] is again "[object Object]" — context decides.
Q3. Why does "5" + 2 differ from "5" - 2?
+ is overloaded: with a string operand it concatenates, so "5" + 2 = "52". Every other arithmetic operator, including -, forces ToNumber, so "5" - 2 = 3.
Q4. Walk through [] == ![] step by step.
![] first: [] is truthy, so ![] is false. Now [] == false → boolean coerces to number 0, giving [] == 0. [] becomes primitive "", then "" == 0 coerces "" to 0, so 0 == 0 → true.