Truthy and falsy values and equality quirks
Falsy: `false`, `0`, `-0`, `0n`, `''`, `null`, `undefined`, `NaN`. Everything else is truthy — including `'0'`, `'false'`, `[]`, `{}`. `==` does type coercion (avoid); `===` doesn't. `NaN !== NaN` (use `Number.isNaN`). `typeof null === 'object'`. Prefer `===` and explicit checks (`Number.isFinite`, `?? `, `Object.is`).
JavaScript's truthiness and equality have a small set of well-defined rules — but they trip up almost everyone because they're inconsistent with intuition.
Falsy values — the complete list (8)
false, 0, -0, 0n, "", null, undefined, NaNEverything else is truthy:
Boolean("0") // true — non-empty string
Boolean("false") // true — non-empty string
Boolean([]) // true — object reference
Boolean({}) // true
Boolean(() => {}) // trueWhy [] is truthy but [] == false is true
if ([]) {} // entered — array is an object
[] == false // true — coercion: [] → '' → 0 == 0This is the canonical example of why == is bad.
=== vs ==
===strict — no coercion; same type required.==loose — coerces both operands using a multi-step table.
Use === always, with one common exception: x == null is a convenient way to test "null or undefined":
x == null // true if x is null OR undefined
x === null // true only if x is nullNaN
NaN === NaN // false
NaN == NaN // false
[NaN].includes(NaN) // true — uses SameValueZero
new Set([NaN]).has(NaN) // true — same
Number.isNaN(NaN) // true — safe check
isNaN("foo") // true — coerces first; misleadingUse Number.isNaN or Object.is(x, NaN).
typeof quirks
typeof null // "object" — historical bug
typeof undefined // "undefined"
typeof NaN // "number"
typeof function(){} // "function"
typeof [] // "object"To check arrays: Array.isArray(x). To check "real object": typeof x === "object" && x !== null && !Array.isArray(x).
+0 vs -0
+0 === -0 // true
Object.is(+0, -0) // false
1 / 0 // Infinity
1 / -0 // -InfinityMatters when dividing or sorting; Object.is is the discriminating check.
Coercion gotchas
[] + [] // ""
[] + {} // "[object Object]"
{} + [] // 0 (in some parsers — block + unary)
1 + "1" // "11" — string wins
1 - "1" // 0 — numeric
"5" * "2" // 10
"5" + 2 // "52"
"5" - 2 // 3Optional chaining + nullish coalescing
user?.profile?.name // safe deep read
const port = config.port ?? 3000 // default only on null/undefined (not 0!)
const port = config.port || 3000 // BUG: 0 becomes 3000?? exists because || is wrong for "give me a default unless explicitly set" when 0 / "" are valid.
The rules to live by
- Always use
===(orx == nullfor the null+undefined check). - Use
??for defaults;||only when "any falsy" is the intent. - Use
Array.isArray,Number.isFinite,Number.isNaN,Object.isfor precise checks. - Don't put expressions you care about inside
if (x)— be explicit (if (x != null),if (x.length > 0)).
Interview framing
"There are exactly 8 falsy values: false, 0, -0, 0n, '', null, undefined, NaN. Everything else — including '0', [], {} — is truthy. == does coercion via a multi-step table and produces surprises like [] == false being true; === never does. NaN !== NaN, so use Number.isNaN or Object.is. typeof null is 'object' for legacy reasons. The modern guidance: === everywhere, ?? for defaults (because || swallows 0 and empty string), and the typed checks (Array.isArray, Number.isFinite) for type discrimination."
Follow-up questions
- •Why is `[] == false` true but `if ([])` enters?
- •Difference between `==`, `===`, `Object.is`, and SameValueZero.
- •When to use `??` vs `||`?
- •Why is `typeof null === 'object'`?
Common mistakes
- •Using `||` for defaults — swallows 0 and ''.
- •Checking NaN with `===`.
- •Treating `[]` and `{}` as falsy.
- •Using `==` 'because it's shorter'.
Performance considerations
- •Negligible. Coercion is cheap; the cost is in correctness, not speed.
Edge cases
- •+0 vs -0 (Object.is discriminates).
- •Symbol comparison.
- •BigInt mixed with Number throws on operators.
- •Object-to-primitive coercion with custom toString/valueOf.
Real-world examples
- •Config defaults: `port ?? 3000` not `port || 3000`.
- •Form-input empty checks — distinguish '' from null deliberately.