Back to JavaScript
JavaScript
easy
junior

What is the difference between double equals and triple equals, and what does coercion actually do?

=== compares value + type; == coerces the operands following a fixed algorithm before comparing. Always use === except for one defensible idiom: `x == null` to check for null OR undefined.

5 min read·~8 min to think through

== (Abstract Equality) coerces operands to a common type before comparing, following an explicit algorithm in the spec. === (Strict Equality) returns false if the types differ — no coercion. The interview question is: do you know why === is the default, and where == is actually defensible?

The == coercion algorithm (the parts that bite).

  • null == undefinedtrue. They equal each other and only each other.
  • NaN == NaNfalse (use Number.isNaN or Object.is).
  • number == string → string is coerced to number: "5" == 5 → true; "" == 0 → true; " " == 0 → true (whitespace coerces to 0).
  • boolean == anything → boolean is coerced to number first: true == 1 (true), false == 0 (true), true == "1" (true), true == "true" (false because "true" → NaN).
  • object == primitive → object is converted via ToPrimitive (calls valueOf then toString): [1] == 1 (true), [1, 2] == "1,2" (true).

These rules produce the famous wat-list: [] == false (true), [] == ![] (true), "0" == false (true) but "0" == "" (false).

=== rules (simple). Same type or false. Same value (with one exception: NaN === NaN is false). +0 === -0 is true. Object.is differs only on these two: Object.is(NaN, NaN) is true and Object.is(+0, -0) is false.

The one defensible == use. x == null is true for both null and undefined — the most concise way to check "is this nullish." Otherwise, ESLint rules (eqeqeq) ban == outright.

Modern alternatives to coercion-based checks.

  • Nullish: x ?? default (null/undefined only) vs x || default (any falsy).
  • Optional chaining: x?.foo short-circuits on null/undefined.
  • Coercion: be explicit — Number(x), String(x), Boolean(x).
  • NaN check: Number.isNaN(x). The global isNaN first coerces (isNaN("foo") → true, surprising).

Reference equality. Both == and === compare objects by reference, not contents: {a:1} === {a:1} is false. For deep equality use JSON.stringify (limited), Lodash _.isEqual, or roll your own.

Output puzzles you should be able to walk through.

ts
[] == ![]      // true: ![] → false → 0; [] → "" → 0; 0 == 0
"" == 0        // true: "" → 0
" \t\n " == 0  // true: whitespace string → 0
null == 0      // false: null only equals undefined
null >= 0      // true: comparison operators coerce; null → 0
"0" == false   // true: false → 0; "0" → 0
0.1 + 0.2 === 0.3 // false: floating point

Bottom line. Use === everywhere except x == null. Prefer explicit conversion (Number(x)) over implicit coercion. ESLint's eqeqeq: ["error", "always", { null: "ignore" }] codifies this exactly.

Code

ts
// Nullish (null OR undefined)
if (x == null) { /* matches null and undefined */ }
// Or:
if (x === null || x === undefined) { /* explicit */ }
// Or modern:
const v = x ?? fallback;

// NaN
if (Number.isNaN(x)) { /* only true NaN */ }

// Same value (NaN-aware)
Object.is(NaN, NaN);    // true
Object.is(+0, -0);      // false
Safe checks

Follow-up questions

  • Why is `[] == ![]` true?
  • What's the difference between Object.is and ===?
  • When is == defensible?
  • Why does `0.1 + 0.2 !== 0.3`?

Common mistakes

  • Using == thinking it's faster — it's not, and it's footgun-laden.
  • Using `||` for defaults when you mean `??` — wipes valid 0/'' values.
  • Comparing objects with === expecting deep equality.
  • Using global isNaN instead of Number.isNaN.

Performance considerations

  • === avoids the coercion machinery — slightly faster in microbenchmarks; irrelevant in practice.
  • Don't reach for Lodash isEqual on a hot path; write a domain-specific comparator.

Edge cases

  • NaN !== NaN — only Number.isNaN / Object.is detect it.
  • +0 === -0 but Object.is differentiates.
  • Document.all behaves as undefined in == — historical browser quirk.

Real-world examples

  • Almost every codebase enforces eqeqeq via ESLint; React source uses Object.is for shallow equality.

Senior engineer discussion

Senior signal: knowing the exact coercion path, the null == undefined idiom, and Object.is vs === distinctions.