Charlie KrugThe Build Log

← All posts

{} + [] 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.

Coax is live. Free, in your browser, no signup.

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.

Comments

Loading comments…