skip to content

In JavaScript, the expression '0' == false evaluates to true. Walk through the loose-equality conversion steps that produce that result.

level: middleimportance: must knowfreq 58%

answer

  1. a boolean is never compared as a boolean
  2. false becomes zero, then the string does
  3. two rounds of conversion, not one
  4. '0' == false versus '' == '0'

basics

~20 s

A boolean operand is converted to a number first, so false becomes 0 and the comparison becomes '0' == 0. The string is then converted with ToNumber to 0, so 0 == 0 is true. Booleans are never compared as booleans.

solid answer

~50 s

Loose equality applies its rules in a fixed order and retries after each conversion. Here the types differ, and one operand is a boolean, so the boolean rule fires first: `false` goes through ToNumber and becomes `0`, leaving `'0' == 0`. Now it is a string against a number, so the *string* is converted with ToNumber, giving `0`, and `0 == 0` is true. The key insight is that **`==` never converts the other side to a boolean** — the boolean is converted away, so this is not a truthiness test. That is why `'0' == false` is true even though the string `'0'` is itself truthy, and why `'' == false` is true while `'' == '0'` is false: two strings share a type, so no conversion happens and they are compared as text.

code

javascript · 6 lines
javascript
console.log(false == 0);   // true: ToNumber(false) is 0
console.log('0' == 0);     // true: ToNumber('0') is 0
console.log('0' == false); // true: both steps applied in order
console.log('' == false);  // true: ToNumber('') is 0
console.log('' == '0');    // false: same type, compared as text
console.log('a' == false); // false: ToNumber('a') is NaN

go deeper

for a junior

Know that comparing anything with == against true or false is not a truthiness test, and that '0' == false is true. In your own code use if (x) rather than x == false.

for a middle

Walk the two conversion rounds out loud: boolean to number, then string to number, with a strict comparison at the end. Be ready to state what ToNumber does to '', whitespace and 'abc'.

for a senior

Point at the real failure — blank text input arriving as '' and matching 0 — and describe parsing once at the boundary with Number() plus a NaN check, so downstream comparisons can be strict.

for a principal

Frame the cost: loose equality is non-transitive, so equality reasoning that holds elsewhere silently fails here. Argue for typed boundaries and a lint policy rather than case-by-case review vigilance.

## The algorithm is ordered, and it retries The abstract equality comparison is not a lookup table; it is a short ordered list of rules where each rule performs one conversion and then re-runs the comparison on the new operands. Getting `'0' == false` right is mostly a matter of applying the rules in the right order and noticing that the process repeats. The order that matters here: 1. Same type → strict comparison, done. 2. null/undefined special case. 3. Number vs String → ToNumber the string, retry. 4. **Boolean on either side → ToNumber that boolean, retry.** 5. Object vs primitive → convert the object to a primitive, retry. 6. Otherwise false. ## Applying it to '0' == false **Round one.** The operands are a string and a boolean. They are not the same type, neither is null or undefined, and this is not a Number-vs-String pair. The boolean rule matches: `ToNumber(false)` is `0`. The comparison restarts as `'0' == 0`. **Round two.** Now it *is* a Number-vs-String pair. `ToNumber('0')` is `0`. The comparison restarts as `0 == 0`. **Round three.** Same type, so strict equality decides: `true`. ```js console.log(false == 0); // true - ToNumber(false) is 0 console.log('0' == 0); // true - ToNumber('0') is 0 console.log('0' == false); // true - both conversions, in that order ``` ## The single most important consequence **Comparing something with `==` against `true` or `false` is not a truthiness test.** People read `x == false` as "is x falsy" and it is not: the boolean is converted to a number and `x` is then compared numerically. The two ideas diverge in both directions. - `'0' == false` is `true`, yet the string `'0'` is truthy — a non-empty string. A truthiness test would have said the opposite. - `[] == false` is `true`, and an array is truthy too. - `'a' == false` is `false`, and so is `'a' == true`: `ToNumber('a')` is `NaN`, and `NaN` is equal to nothing, including itself. That last case means a value can be loosely equal to neither `true` nor `false`, which is impossible for a real boolean test. If you want a truthiness test, write `if (x)` or `if (!x)`; there is never a good reason to write `x == false`. ## The string side: what ToNumber does to strings ToNumber on a string trims leading and trailing whitespace, treats the empty (or all-whitespace) string as `0`, parses numeric literals including `'0x1f'`, `'1e3'`, `'Infinity'` and signs, and yields `NaN` for anything else. This produces several results people find surprising: ```js console.log('' == 0); // true - empty string is 0 console.log(' ' == 0); // true - whitespace-only is 0 console.log('\n' == 0); // true console.log('0x10' == 16); // true - hex literals parse console.log('abc' == 0); // false - ToNumber('abc') is NaN ``` ## Where the transitivity breaks Put two of these together and loose equality visibly fails a property people expect from equality: ```js console.log('' == 0); // true console.log('0' == 0); // true console.log('' == '0'); // false <-- not transitive ``` The last line is `false` because both operands are strings — the same type — so rule 1 fires and they are compared as text without any conversion. The conversions that made the first two lines true are simply not reachable when the types already match. An interviewer who asks for the walkthrough of `'0' == false` is usually setting up this follow-up. ## The bug this causes in real code The realistic version is a value read from a text source — a form field, a query string, a config file, a CSV column — compared against a boolean or a number with `==`. A field left blank arrives as `''`, and `'' == 0` reports true, so "no answer" is silently processed as "zero". Or a flag stored as the string `'0'` is compared with `== false`, works by accident, and then breaks the day someone switches the check to `!value` — because `'0'` is truthy and the two forms disagree. The fix is to convert deliberately at the boundary and compare strictly afterwards: parse the string once with `Number(...)`, validate the result is not `NaN`, and use `===` everywhere downstream. Then a type mismatch shows up as a failed comparison you can see, instead of a coercion the operator performed on your behalf.

  • Given that '' == 0 and '0' == 0 are both true, what does '' == '0' evaluate to, and why?
    It is `false`. Both operands are strings, so the algorithm's first rule applies and compares them strictly as text — no ToNumber conversion is reachable. This makes `==` non-transitive, which is the concrete reason it cannot be reasoned about the way an equality operator normally can.
  • Why is 'a' == false false, when 'a' == true is also false?
    The boolean becomes a number first, so the comparisons are `'a' == 0` and `'a' == 1`. `ToNumber('a')` is `NaN`, and `NaN` is equal to no value at all, so both are false. A real truthiness test could never return false for both branches — further proof that `== false` is not a falsiness check.
  • How would you rewrite a check like if (input == false) to say what it actually means?
    Decide which meaning you want. For a truthiness test write `if (!input)`. For "the parsed number is zero" write `const n = Number(input); if (!Number.isNaN(n) && n === 0)`. For "the value is literally the boolean false" write `if (input === false)`. Each is explicit and none of them silently accepts `'0'`, `''` and `[]` together.

saying these in an interview costs you the question

  • x == false is a way to test whether x is falsy
  • The string operand is converted to a boolean
  • '' == '0' must be true because both equal 0
  • Only one conversion happens before comparing
  • 'abc' == false is true because 'abc' is not a boolean

context