Type coercion is the part of the language with the worst reputation: even people who don't write JavaScript know the memes about [] + {}. But behind the "madness" lies a small set of rules, and whoever knows them reads such code calmly — and, more importantly, understands where coercion will fire silently and quietly corrupt the data.
Explicit and implicit
Explicit coercion — you ask for it yourself:
Number("42"); // 42
String(42); // "42"
Boolean(0); // false
parseInt("42px", 10); // 42 — parses as far as it can
Number("42px"); // NaN — this one is strict
Implicit — the language does it on its own when types don't match:
"5" - 1; // 4 — minus works only with numbers: string → number
"5" + 1; // "51" — plus with a string CONCATENATES: number → string
1 < 2 < 3; // true, but not for the reason you think: (1<2)=true, true<3 → 1<3
Remember the asymmetry of +: if even one operand is a string, it's concatenation. All the other arithmetic operators pull toward numbers. Hence the classic bug: a number from input.value (which is always a string!) "adds up" with a number into "105" instead of 15.
truthy and falsy: the short list of lies
In a boolean context (if, !, &&, ||) every value becomes true or false. There are exactly eight falsy values — easier to memorize than to guess:
false, 0, -0, 0n, "", null, undefined, NaN
Everything else is truthy. Including the unexpected:
Boolean([]); // true — an empty array is truthy!
Boolean({}); // true
Boolean("0"); // true — a non-empty string
Boolean("false"); // true
Hence the trap: if (items) {} does not check that the array is non-empty — it's always truthy. Non-emptiness is checked explicitly: if (items.length > 0).
== versus ===: what loose comparison actually does
=== compares without coercion: different types — false right away. == coerces the operands to a common type and compares afterwards. Its rules are shorter than they seem:
- number == string → the string is coerced to a number;
- anything == boolean → the boolean is coerced to a number (here's where the hell is:
"1" == true, because true → 1); - object == primitive → the object is coerced to a primitive (ToPrimitive:
valueOf, thentoString); null == undefined→ true, and null equals nothing else.
"42" == 42; // true (string → number)
"" == 0; // true ("" → 0)
"0" == false; // true (false → 0, "0" → 0)
[] == ""; // true ([] → "")
null == 0; // false! null equals only undefined
NaN == NaN; // false — NaN equals nothing
The practical team rule: === everywhere, with the single widely accepted exception of x == null as a compact check for "null or undefined". This exception is so well established that linters and colleagues understand it.
The memes, dissected by the rules
[] + []; // "" — both arrays → toString → "" + ""
[] + {}; // "[object Object]" — "" + "[object Object]"
{} + []; // 0! — {} at the start of a line is parsed as a CODE BLOCK, leaving +[] → 0
"2" * "3"; // 6 — multiplication pulls toward numbers
Not a single line here is random — everything follows from the rules above plus one fact about the parser (curly braces at the start of an expression are a block, not an object). You shouldn't write like this, of course; being able to read it — you should.
Where coercion bites in real code
input.valueand data from the URL are always strings. Before arithmetic and comparison — an explicitNumber().- Sorting numbers:
[10, 9, 1].sort()yields[1, 10, 9]— sort compares strings by default. You need a comparator:sort((a, b) => a - b). - "Empty" checks:
if (count)will skip a legitimate zero. If 0 is a valid value, checkcount != nullorcount !== undefined. - Object keys are always strings:
obj[1]andobj["1"]are the same property.
In short
+with a string concatenates; the rest of arithmetic pulls toward numbers.Number()is strict,parseInt()parses until the first error.- There are only eight falsy values:
false, 0, -0, 0n, "", null, undefined, NaN. An empty array and an empty object are truthy. ==coerces types by short rules; a boolean in a comparison becomes a number first.null == undefined, and null equals nothing else.- The team rule:
===everywhere,x == nullis the only exception. - Real bites: the string
input.value, the string-basedsort()by default,if (count)versus zero.
What to read next
- Types in JavaScript — the foundation: which types exist at all and where NaN lives.
- Scope: var, let, const — the next topic in the series.
- JavaScript essentials for TypeScript — how TypeScript closes off some of these traps at compile time.