{} + [] is 0, and coercion has nothing to do with it.
Coax runs the real ECMAScript coercion algorithm on any two values you type: ToPrimitive to ToNumber to the operator, each spec step cited by section. Not the meme table, the actual engine.
Type {} + [] at a Node prompt and you get 0. Type [] + {} and you get "[object Object]".
The usual lesson is that JavaScript is haunted and you should memorize both. The real lesson is
stranger: those two lines don't even run the same kind of code. One is type coercion. The other
is a parsing accident, and coercion never touches it.
At the top level of a statement, the leading {} in {} + [] parses as an empty block, not an
object literal. So the line is really the unary expression +[], which is ToNumber of an empty
array, which is 0. Flip the operands to [] + {} and now both values genuinely get coerced and
concatenated. Two answers, two completely different mechanisms, and the ten-item meme table
flattens them into a single row you're told to remember.
The spec is the tool
Every explainer on coercion is the same handful of party tricks: [] + [], '5' + 3, 1 == '1',
NaN === NaN. They're memorable, but they teach memorization, not the machine underneath. The
moment you hit a real bug on a value that isn't in the famous ten, an object with a custom
valueOf, a nested array, a number that arrived from a form field as a string, the meme tables
are useless and you're back to guessing in a scratch REPL.
The mechanism behind all of it is not secret and not even that complicated. ToPrimitive,
ToNumber, ToString, ToBoolean, and the Abstract Equality Comparison are a short,
deterministic algorithm published in ECMA-262. Almost nobody reads
it, because the spec is dense and nobody built a tool that turns "go read the spec" into "type
your two values and watch the spec run." Coax is that tool.
An engine, not a lookup table
The important decision is that Coax implements those abstract operations as real TypeScript
functions, not a table of known gotchas. Each function optionally records every intermediate step
into a Tracer: the operation name, its spec section, the input, the output, and a plain-English
detail. The UI is a thin layer on top. Two operand inputs, an operator strip (==, +,
Boolean() per operand, and template-literal interpolation), and a rendered trace of whatever the
engine actually did for the exact pair of values you typed. Here is [] + {} as the app prints it:
1. OrdinaryToPrimitive 7.1.1.1 [].valueOf() returned an object, skipped
2. OrdinaryToPrimitive 7.1.1.1 [].toString() -> ""
3. OrdinaryToPrimitive 7.1.1.1 {}.valueOf() returned an object, skipped
4. OrdinaryToPrimitive 7.1.1.1 {}.toString() -> "[object Object]"
5. + 13.15.3 one primitive is a string, so concatenate
A + B = "[object Object]"
Because the engine is general rather than hardcoded, it is correct for pairs nobody thought to
enshrine in a blog post. That is the whole edge over the meme sites. It isn't reciting the answer
to [] + {}, it's running the same ToPrimitive machinery on your object with the weird
valueOf, and it shows its work either way. The operands are parsed by a restricted literal
grammar, never eval, so an unsupported input shows an inline error instead of running something.
The honest boundary
The parsing quirk from the top gets its own callout: when operand A is {} and the operator is
+, a panel shows both the expression-context result and the statement-context 0, and explains
why they disagree. That's the difference between a tool that knows the spec and one that memorized
outputs.
Coax is spec-accurate, not spec-complete. It covers primitives, plain objects, and arrays.
Symbol.toPrimitive, BigInt, and exotic objects like Proxies or Dates with a custom valueOf
are real spec paths but out of scope for v1, and the UI marks that boundary instead of silently
getting it wrong. The engine ships behind 140 tests and no backend at all: everything you type
stays in the tab, because it's a static bundle with nowhere to send it.
Try it
Open Coax, drop [] in the first slot and {} in the
second, hit +, then swap the two. You'll get two different results, each with its own numbered
trace, plus the parsing callout explaining why {} + [] is the odd one out. The
source is on GitHub.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…