← back to the section

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, then toString);
  • 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.value and data from the URL are always strings. Before arithmetic and comparison — an explicit Number().
  • 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, check count != null or count !== undefined.
  • Object keys are always strings: obj[1] and obj["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 == null is the only exception.
  • Real bites: the string input.value, the string-based sort() by default, if (count) versus zero.
  • 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.