Back to JavaScript
JavaScript
easy
mid

What are truthy and falsy values in JavaScript, and what are the common 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`).

4 min read·~10 min to think through

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)

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

Everything else is truthy:

js
Boolean("0")      // true — non-empty string
Boolean("false")  // true — non-empty string
Boolean([])       // true — object reference
Boolean({})       // true
Boolean(() => {}) // true

Why [] is truthy but [] == false is true

js
if ([]) {}            // entered — array is an object
[] == false           // true — coercion: [] → '' → 0 == 0

This 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":

js
x == null      // true if x is null OR undefined
x === null     // true only if x is null

NaN

js
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; misleading

Use Number.isNaN or Object.is(x, NaN).

typeof quirks

js
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

js
+0 === -0          // true
Object.is(+0, -0)  // false
1 / 0              // Infinity
1 / -0             // -Infinity

Matters when dividing or sorting; Object.is is the discriminating check.

Coercion gotchas

js
[] + []        // ""
[] + {}        // "[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        // 3

Optional chaining + nullish coalescing

js
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

  1. Always use === (or x == null for the null+undefined check).
  2. Use ?? for defaults; || only when "any falsy" is the intent.
  3. Use Array.isArray, Number.isFinite, Number.isNaN, Object.is for precise checks.
  4. 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.

Senior engineer discussion

Seniors avoid `==` (except the `x == null` idiom), use `??` for defaults, reach for typed predicates (`Array.isArray`, `Number.isFinite`), and write explicit conditions rather than relying on truthiness when the distinction matters.

Related questions