Coercion, Equality & Number Precision
In one line
Coercion is a small set of deterministic conversion rules, not chaos — and the parts that genuinely bite are number precision and the == table, not the meme examples.
What it is
Coercion happens through three abstract operations. ToPrimitive converts an object to a primitive by calling Symbol.toPrimitive, then valueOf, then toString (order depends on the requested hint). ToNumber and ToString then convert primitives. Every surprising result follows from these applied in order.
[] + {} producing "[object Object]" isn't arbitrary: + with a non-number operand converts both to primitives; [] stringifies to "" and {} to "[object Object]". Understanding the mechanism matters more than memorising the outputs, because the mechanism is what shows up in real code — "5" - 2 is 3 while "5" + 2 is "52", and a template literal calling toString on an object you expected to be a string.
== applies coercion before comparing; === compares type and value directly. The == rules worth actually knowing: null == undefined is true and neither equals anything else, NaN never equals itself, and string/number comparison converts the string to a number. Use === by default. The one defensible == is x == null as a null-or-undefined check, and it's common enough in real code to be worth recognising.
Object.is differs from === in exactly two cases: it treats NaN as equal to itself, and distinguishes +0 from -0. That's it. It's what React.useState and Object.freeze-adjacent comparison logic use internally.
Truthiness has a short falsy list: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including [], {}, and "0". The common bug is if (count) failing when count is 0, which is what ?? and ?. exist to fix. ?? falls back only on null/undefined, where || falls back on any falsy value — the distinction that keeps a legitimate 0 or "" from being replaced by a default.
Numbers are IEEE-754 doubles. 0.1 + 0.2 !== 0.3 is not a JavaScript flaw; it's binary floating point, and every language with doubles does it. The consequences are practical: never store currency as a float — use integer minor units or a decimal library — and be aware that integers above Number.MAX_SAFE_INTEGER (2^53 − 1) lose precision, which silently corrupts 64-bit IDs coming from a backend. BigInt handles arbitrary-precision integers but doesn't mix with Number in arithmetic.
Why it matters
The floating-point and MAX_SAFE_INTEGER parts cause genuine production bugs — a Twitter-style snowflake ID parsed as a Number is corrupted before it reaches your code, and totals that are off by a cent come from float arithmetic. Knowing to request IDs as strings is a real senior instinct.
The == and truthiness material is standard screen fodder, and the ?? versus || distinction is the kind of small correctness detail reviewers notice in a take-home.
Key points
- Coercion is
ToPrimitive→ToNumber/ToStringapplied in a defined order; every surprising result is derivable from those rules rather than special-cased. ===compares type and value with no conversion;==converts first, and the only broadly defensible use isx == nullto test for null-or-undefined together.Object.isdiffers from===in exactly two places:NaNequals itself, and+0is distinct from-0.- The falsy list is
false,0,-0,0n,"",null,undefined,NaN— empty arrays and empty objects are truthy. ??falls back only onnull/undefinedwhile||falls back on any falsy value, which is why||silently replaces a legitimate0or empty string.- All JavaScript numbers are IEEE-754 doubles, so
0.1 + 0.2 !== 0.3and currency must be handled as integer minor units or with a decimal library. - Integers beyond
Number.MAX_SAFE_INTEGERlose precision silently — 64-bit database IDs should cross the wire as strings, not numbers. NaNis the only value not equal to itself, which is whyNumber.isNaNexists and why[NaN].includes(NaN)istruewhileindexOffails to find it.