skip to content

Why does the JavaScript expression [] == ![] evaluate to true? Derive it from the loose-equality algorithm.

level: middleimportance: should knowfreq 44%

answer

  1. evaluate the ! before anything else
  2. an array is an object, and objects are truthy
  3. the array turns into its joined string
  4. empty string and zero meet in the middle

basics

~20 s

The ! is applied first and yields false, because an array is truthy. Comparing [] == false converts false to 0, then converts the array to its primitive form, the empty string, which converts to 0 — so 0 == 0 is true.

solid answer

~50 s

Take it one step at a time. `![]` is evaluated before the comparison: an array is an object and every object is truthy, so `![]` is `false`. That leaves `[] == false`. A boolean operand is converted to a number, so the comparison becomes `[] == 0`. Now an object is being compared with a number, so the object is converted to a primitive — for an array that produces its joined string, which for an empty array is `''`. That leaves `'' == 0`, a string against a number, so the string goes through ToNumber and becomes `0`, giving `0 == 0`, which is true. The point of the puzzle is not the answer but the method: four ordinary rules applied in order, each visible in the specification, none of them special-cased for arrays.

code

javascript · 6 lines
javascript
console.log(![]);           // false - arrays are truthy
console.log([] == false);   // true
console.log([].toString()); // '' - array converted to a primitive
console.log('' == 0);       // true
console.log([] == ![]);     // true
console.log([] == []);      // false - two distinct objects

go deeper

for a junior

Know that arrays and all other objects are truthy, so ![] is false, and that comparing an array with == against a primitive converts the array to a string first.

for a middle

Derive the whole chain in order — precedence, boolean to number, object to primitive, string to number — and be ready to contrast it with [] == [] being false because same-type operands skip coercion.

for a senior

Draw the operational lesson: an == comparison against an object runs that object's conversion code, so equality can execute application logic. Prefer explicit checks such as arr.length === 0 or Array.isArray.

for a principal

Use the example to argue policy rather than trivia: an operator whose behaviour depends on user-defined conversion hooks is unreviewable at scale, which is why banning == with objects belongs in tooling rather than in code review.

## The famous line, unpacked `[] == ![]` is the party trick of JavaScript coercion. It is worth knowing not because the expression appears in real code — it does not — but because deriving it end to end proves you can run the loose-equality algorithm on operands you have never seen before, instead of recalling a table. ## Step 0: precedence Before any comparison happens, `![]` is evaluated. Unary `!` binds tighter than `==`, so the expression is `[] == (![])`, never `([] == []) !`. An array is an object, all objects are truthy, so `![]` is `false`. The expression is now `[] == false`. This first step catches a surprising number of candidates, who assume both sides are arrays and answer "false, because two arrays are different objects". That answer is correct for `[] == []`, which really is `false` — but that is a different expression. ## Step 1: the boolean rule The operands are an object and a boolean. The types differ, neither is null or undefined, and this is not a Number-vs-String pair, so the boolean rule fires: the boolean is converted with ToNumber. `false` becomes `0`. Retry with `[] == 0`. Note what did **not** happen: the array was not converted to a boolean. Loose equality removes the boolean rather than producing one, which is why this is not a truthiness comparison. ## Step 2: object versus primitive Now an object is compared with a number. The rule for that case converts the object to a primitive value and retries. For an array, that conversion ends up calling `toString()`, which joins the elements with commas. An empty array joins to the empty string. Retry with `'' == 0`. The same rule explains the family of results around it: ```js console.log([] == 0); // true - '' then 0 console.log([0] == 0); // true - '0' then 0 console.log([1] == 1); // true - '1' then 1 console.log([1, 2] == '1,2'); // true - joined string, no further conversion console.log([1, 2] == 12); // false - '1,2' is NaN as a number ``` ## Step 3: string versus number `'' == 0` is a string against a number, so the string is converted: `ToNumber('')` is `0`. Retry with `0 == 0`. ## Step 4: same type Two numbers now, so the algorithm delegates to strict equality, and `0 === 0` is `true`. Final answer: `[] == ![]` is `true`. ```js console.log(![]); // false console.log([] == false); // true console.log([].toString()); // '' (empty string) console.log('' == 0); // true console.log([] == ![]); // true ``` ## The contrast worth pairing it with `[] == []` is `false`. Two array literals are two distinct objects; the operands share a type, so the algorithm's first rule applies and compares references, which differ. Coercion of an object happens **only** when the other operand is a primitive. Being able to state both results and explain why they differ is the real signal here — one expression exercises four coercion rules, the other exercises none. The same holds for `{} == {}`, `[] == {}` (false — two objects, different references) and `[] == '0'` (false — the array becomes `''`, and `'' === '0'` is false since both are then strings). ## What this tells you about writing code The practical takeaway is not a rule about arrays; it is that `==` with an object operand triggers a user-visible conversion step. If the object has a custom conversion, that code runs during a comparison — an equality check can execute arbitrary logic, which is a genuinely surprising property for an operator. Comparing objects with `==` against primitives is therefore something to avoid outright: use `===` for reference identity, or extract the field you actually mean to compare (`arr.length === 0`, `arr[0] === 1`) so the comparison names its own subject. When you need to know whether an array is empty, `arr.length === 0` says so directly. When you need to know whether a value is an array at all, `Array.isArray(value)` answers it without any coercion. Neither of those can be derailed by a conversion you did not intend to trigger. ## How to answer under pressure Say the steps out loud in order and do not skip precedence: `![]` is false; boolean becomes 0; array becomes `''`; `''` becomes 0; `0 == 0` is true. Then volunteer `[] == []` being false as the contrast. That sequence takes fifteen seconds and demonstrates exactly what the question is testing.

  • Why is [] == [] false when [] == ![] is true?
    Because the operands of `[] == []` share a type. The algorithm's first rule sends same-type operands to strict equality, which compares object references — and two array literals are two distinct objects. No conversion is ever attempted. In `[] == ![]` the right side is a boolean, so the differing types unlock the conversion rules.
  • What does [1, 2] == '1,2' evaluate to?
    `true`. An object compared with a string is converted to a primitive, and an array's string form is its elements joined with commas, giving `'1,2'`. Both operands are then strings of identical text, so the strict comparison succeeds. `[1, 2] == 12` is false by contrast, because `ToNumber('1,2')` is `NaN`.
  • Does comparing an object with == ever run application code?
    Yes. When an object is compared with a primitive, loose equality converts the object to a primitive, which invokes the object's own conversion methods. A custom `toString`, `valueOf` or `Symbol.toPrimitive` therefore executes during what looks like a simple comparison, and can even return a different value each time. It is one more reason to keep `==` away from objects.

saying these in an interview costs you the question

  • Both sides are arrays, so the answer is false
  • ![] evaluates to an empty array
  • An empty array is falsy, so ![] is true
  • The array's reference is converted to a number
  • [] == [] is true because both are empty

context