Why does the JavaScript expression [] == ![] evaluate to true? Derive it from the loose-equality algorithm.
answer
- evaluate the ! before anything else
- an array is an object, and objects are truthy
- the array turns into its joined string
- empty string and zero meet in the middle
basics
~20 sThe ! 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 sTake 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 linesconsole.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 objectsgo deeper
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.
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.
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.
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