Type Coercion & Equality

Why == is dangerous, how JS coerces types automatically, and the truthy/falsy rules that trip up even experienced devs.

must medium โฑ 18 min coercionequalitytruthy-falsytypeofNaN
Mastery:
Why interviewers ask this
Interviewers use coercion puzzles to test whether you understand the language deeply or just use it. These are common screen questions precisely because they surprise people.

JavaScript has two kinds of equality and an automatic type-conversion system that runs silently. Understanding both is essential for reading legacy code and for answering the coercion puzzles that appear in screens.

Strict vs loose equality

=== (strict) compares type and value โ€” no coercion. == (loose) first coerces both operands to a common type, then compares. The coercion rules are complex enough that you should use === by default and only reach for == when you explicitly want type flexibility (e.g., value == null to catch both null and undefined).

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

The [] == ![] case is the classic puzzle. ![] evaluates to false (arrays are truthy), so you get [] == false. Then [] converts to "", which converts to 0, and false converts to 0. Both sides are 0 โ€” true. This is why you avoid ==.

The falsy seven

Exactly seven values are falsy in JavaScript โ€” everything else is truthy:

false, 0, -0, 0n, "", null, undefined, NaN

Common surprises: [] and {} are truthy (even empty). "0" is truthy. new Boolean(false) is truthy (itโ€™s an object). document.all is a historical exception but ignore that.

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 

Type coercion rules

When JS needs to convert a value, it uses one of three abstract operations:

  • ToNumber: "" โ†’ 0, "3" โ†’ 3, null โ†’ 0, undefined โ†’ NaN, true โ†’ 1, false โ†’ 0, [] โ†’ 0, [1] โ†’ 1, [1,2] โ†’ NaN.
  • ToString: null โ†’ "null", undefined โ†’ "undefined", [] โ†’ "", {} โ†’ "[object Object]", numbers use their numeric string.
  • ToPrimitive: for objects, calls [Symbol.toPrimitive], then .valueOf(), then .toString().

The + operator is the sneakiest โ€” it prefers string concatenation if either operand is a string:

1 + "2"     // "12"   โ€” string wins
1 + 2 + "3" // "33"  โ€” left to right: 3 then "3"
"3" + 2 + 1 // "321" โ€” "3" first poisons everything
+"3"        // 3     โ€” unary + triggers ToNumber
+[]         // 0
+{}         // NaN

typeof and the null bug

typeof returns a string describing the type โ€” with one infamous bug:

typeof 42          // "number"
typeof "hi"        // "string"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof Symbol()    // "symbol"
typeof 42n         // "bigint"
typeof function(){} // "function"
typeof {}          // "object"
typeof []          // "object"   โ€” arrays are objects
typeof null        // "object"   โ† the bug, baked in since 1995

null is not an object โ€” itโ€™s its own primitive type. The typeof null === "object" result is a historical bug that canโ€™t be fixed without breaking the web. To check for null: use value === null.

NaN โ€” the only value not equal to itself

NaN (Not a Number) is the result of invalid numeric operations. Itโ€™s infectious and bizarre:

NaN === NaN       // false โ€” NaN is never equal to anything including itself
isNaN("hello")    // true  โ€” but isNaN coerces first! ("hello" โ†’ NaN)
Number.isNaN("hello") // false โ€” strict, only true for actual NaN values
Number.isNaN(NaN)     // true  โ€” the right check
Number.isFinite(Infinity) // false
Object.is(NaN, NaN)   // true  โ€” Object.is uses SameValue, not ===

Always use Number.isNaN(), not global isNaN().

Object.is โ€” SameValue equality

Object.is(a, b) is like === but handles two edge cases differently:

Object.is(NaN, NaN)   // true  (=== returns false)
Object.is(+0, -0)     // false (=== returns true)

This is what React uses internally to compare state values for bail-outs.

Say it out loud
โ€œUse === everywhere by default โ€” it never coerces. == triggers abstract equality coercion: operands are converted via ToNumber/ToString/ToPrimitive until they share a type. The seven falsy values are false, 0, -0, 0n, "", null, undefined, and NaN. typeof null is 'object' โ€” a legacy bug. NaN is not equal to itself, so use Number.isNaN() to detect it. Object.is is stricter than === and correctly handles NaN and -0.โ€

Likely follow-up questions
  • What does `[] == ![]` evaluate to, and why?
  • Why is `typeof null === 'object'` โ€” is null an object?
  • How do you reliably check for NaN?
  • What values are falsy in JavaScript?
  • When is it actually safe to use ==?

References