cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Type coercion

Understand the abstract operations behind JavaScript’s implicit conversions: ToPrimitive, ToNumber, ToString, ToBoolean, plus string concatenation and classic corner cases.

By the end, you can
  • 01
    Trace the translatorsExplain when ToPrimitive, ToNumber, ToString, and ToBoolean run.
  • 02
    Predict + correctlyDecide whether binary + adds numbers or concatenates strings.
  • 03
    Defuse corner casesWalk through puzzles like [] == ![] without memorizing them.

Coercion is translation

Type coercion is JavaScript converting a value from one type to another because an operator, condition, or built-in operation needs a different kind of value. You already met the practical rules in the Type conversion lesson. This lesson explains the internal translators behind those rules.

The most important translators are ToPrimitive, ToNumber, ToString, and ToBoolean. They are called abstract operations: names the specification uses to describe what the language must do. You do not call them directly, but you see their results every time code like [] + or if (value) runs.

The short version

Operators choose translators. Objects first become primitives. Then the operator decides whether it needs numbers, strings, or a simple truthy/falsy check.

Real-life analogyThe engine has internal translators

Imagine each operator at a counter. Before it can work, it asks the value for the form it understands. The * counter wants numbers. The if counter wants a yes/no truthiness answer. The + counter is picky: first it asks both sides for primitives, then it checks whether either primitive is a string.

In real life: A passport clerk asks for a plain ID before processing a request
In JavaScript: ToPrimitive asks an object for a primitive value
In real life: A currency desk turns bills into the currency the shop accepts
In JavaScript: ToNumber or ToString translates primitives for an operator
In real life: A bouncer checks one short yes/no list
In JavaScript: ToBoolean checks the falsy list

Where the analogy stops: The engine is not literally calling human translators, and modern engines optimize aggressively. The analogy is about the specified result, not implementation details.

We will use small, real examples and a tracer for supported puzzles. The deeper spec tour later, A tour of the abstract operations, will revisit the formal algorithms in full.

ToPrimitive comes first

STEP THROUGH

Primitive values are strings, numbers, booleans, null, undefined, symbols, and bigints. Objects are not primitive, so an operator that needs a plain value must ask the object to produce one. That request is ToPrimitive.

For ordinary objects, JavaScript tries methods such as valueOf() and toString() in an order chosen by a hint: number, string, or default. The Object to primitive conversion lesson covers custom Symbol.toPrimitive, valueOf, and toString in detail; here we focus on what coercion does next.

Watch ToPrimitive choose a plain value
Step 0 of 7Ready
Your turn: follow the blue line

A custom object lets us watch the first translator, ToPrimitive, choose a primitive before the operator continues.

Running in
  1. script
Next: line 7
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const value = {  [Symbol.toPrimitive](hint) {    return hint === "number" ? 7 : "seven";  }}; console.log(String(value));console.log(value + "!");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Notice line 9: binary + does not immediately add. It asks the object for a primitive with the default hint. Because the object returns a string, + concatenates.

Real-life analogyObjects are vending machines

An object is like a vending machine. The operator cannot drink the machine; it needs the machine to dispense something plain first. The hint is the button the operator presses.

In real life: The machine is not the drink
In JavaScript: The object is not the primitive
In real life: Press water or soda
In JavaScript: The hint asks for a preferred form
In real life: The machine must dispense an item
In JavaScript: ToPrimitive must return a primitive or throw

Where the analogy stops: Real vending machines can run out. JavaScript objects can compute, return different primitives, or throw; the analogy only shows the object-to-plain-value step.

ToNumber & ToString

INTERACTIVE

Once values are primitive, operators usually ask for a number or a string. Multiplication, subtraction, unary plus, and numeric comparison go toward ToNumber. String concatenation, template text, and many display operations go toward ToString.

Selected ToNumber rules
ExpressionResultWhy
Number(" 0x10 ")16Whitespace is trimmed, and 0x reads as hexadecimal.
Number("0b101")5Binary string prefixes work in Number().
Number("1e3")1000Exponent notation is numeric.
Number("Infinity")InfinityInfinity is a numeric string JavaScript recognizes.
Number("")0The empty string becomes 0, one of the classic surprises.
Number(true)1true becomes 1; false becomes 0.
Number(null)0null becomes 0.
Number(undefined)NaNundefined becomes NaN.
+1nTypeErrorUnary plus uses ToNumber, and ToNumber rejects BigInt.
Number(Symbol())TypeErrorSymbols refuse numeric conversion.
Selected ToString rules
ExpressionResultWhy
String(-0)"0"The sign is not preserved when -0 becomes text.
String([1, 2])"1,2"Arrays stringify by joining elements with commas.
String({})"[object Object]"Plain objects inherit Object.prototype.toString.
String(Symbol('id'))"Symbol(id)"Explicit String() works for symbols.
"" + Symbol("id")TypeErrorImplicit string conversion through + throws for symbols.

The next playground traces real expressions through a small set of supported shapes. Pick a preset, read the code, then compare the abstract-operation story with the final value.

Coercion tracer
Expression to tracePop out in the code editor (opens in a new tab)JavaScript
console.log([] + {});
Trace"[object Object]"
1
ToPrimitive([])

Array uses the default hint. valueOf gives the array object, then toString joins its elements.

""
2
ToPrimitive({})

Plain object valueOf is still an object, so toString supplies the primitive.

"[object Object]"
3
+ decision

At least one primitive is a string, so + concatenates strings instead of adding numbers.

"" + "[object Object]"
4
Final result

The empty string disappears, leaving the object tag.

"[object Object]"
Try it yourself

[] + {} produces "[object Object]". Both operands become primitives before + decides to concatenate.

These traces are a teaching mirror of the spec steps for the listed expressions, not a general JavaScript interpreter. Tests compare every final value with real JavaScript.
Symbols are intentionally strict

String(Symbol("id")) works because it is explicit. But implicit string conversion through "" + Symbol("id") throws. That strictness prevents symbols from silently becoming property names or display text where you did not ask for it.

ToBoolean checks the short falsy list

ToBoolean is refreshingly small. It does not parse strings and it does not ask objects for primitives. It checks whether the value is one of the falsy values; everything else is truthy.

Falsy values
Falsy valueNotes
falseAlready the boolean false.
0, -0, 0nZero numbers and zero bigint.
""The empty string only. Strings like "0" and "false" are truthy.
nullMissing on purpose.
undefinedMissing by default.
NaNNot a valid number.
document.allA historic browser-only exception. Mention it, do not depend on it.
Truthiness is not numeric conversionPop out in the code editor (opens in a new tab)JavaScript
Boolean([]);      // true: objects are truthy
Boolean("0");     // true: non-empty string
Boolean(0);       // false: zero is falsy
Boolean(Number("nope")); // false: NaN is falsy

This is why if (items) does not check whether an array is empty. Empty arrays are objects, and objects are truthy. Test the length when length is what you mean.

+ with strings: add or concatenate?

STEP THROUGH

Binary + is the operator with the most famous coercion surprises. Its decision tree is:

  1. Evaluate the left expression, then the right expression.
  2. Run ToPrimitive on objects with the default hint.
  3. If either primitive is a string, convert both to strings and concatenate.
  4. Otherwise, convert both toward numeric values and add.
Left-to-right + decisions
Step 0 of 3Ready
Your turn: follow the blue line

JavaScript evaluates the expression from left to right. Each + decides with the values it has at that moment.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(result);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose an expression to replay

Changing the expression resets the recording so you can predict again.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The decision happens at each +, left to right. That is why 1 + 2 + "3" becomes "33", but "1" + 2 + 3 becomes "123".

Adds or concatenates?
  • true + 1
  • 1 + 2
  • null + 5
  • [] + []
  • "" + 0
  • ({ toString() { return "4"; } }) + 1
Try it yourself
0 of 6 correct

Sort each + expression by what the + operation ultimately does. Read the explanation even when your guess is right.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Corner cases without panic

Coercion puzzles look random when you memorize outputs. They become manageable when you name the translators. Here is a gallery of classic cases, all verified by tests.

Classic coercion corner cases
ExpressionResultTrace
[] + []""Both arrays ToPrimitive to empty strings; + concatenates.
[] + {}"[object Object]"[] becomes ""; {} becomes "[object Object]".
({}) + []"[object Object]"As an expression, the object is an object literal. In statement position, some consoles parse {} as a block before +[].
"b" + "a" + +"a" + "a""baNaNa"+"a" is NaN; string + turns NaN into "NaN".
[] == ![]true![] is false; == then converts false to 0 and [] to "" then 0. Equality gets the next lesson.
undefined + 1NaNundefined becomes NaN, and arithmetic with NaN stays NaN.
0.1 + 0.20.30000000000000004This is floating-point precision from the Numbers lesson, not coercion.
Trace [] == ![]JavaScript
[] == ![]
[] == false      // ![] is false because arrays are truthy
[] == 0          // false becomes 0
"" == 0          // [] becomes "" through ToPrimitive
0 == 0           // "" becomes 0
true
Statement position changes the first token

In expression form, () + [] is the string "[object Object]". In a console where you type + [] as a whole statement, the leading braces may be parsed as an empty block, leaving +[], which is 0. Parentheses force the expression form.

Use coercion deliberately in real code

Professionals do not avoid coercion by pretending it does not exist. They make conversion points obvious. Convert at the boundary where data enters your program, then keep the rest of the code boring.

Convert form text before addingPop out in the code editor (opens in a new tab)JavaScript
function checkoutTotal(form) {
  const price = Number(form.price.value);
  const shipping = Number(form.shipping.value);

  if (Number.isNaN(price) || Number.isNaN(shipping)) {
    return "Please enter valid numbers";
  }

  return price + shipping;
}

Form fields, URL search parameters, and JSON from APIs often arrive as strings. Convert once with Number(), String(), or Boolean() when that is what you mean. Do not let + guess whether a checkout total is math or text.

Common misconceptions

“Coercion is random.”

It is specified. The results can be surprising, but the same abstract operations explain them.

“ToBoolean converts objects with valueOf or toString.”

No. Ordinary objects are always truthy. ToBoolean does not call ToPrimitive.

“+ always adds after Number().”

Only when neither primitive is a string. A single string makes binary + concatenate.

“String(symbol) and "" + symbol are the same.”

Explicit String() can describe a symbol. Implicit string conversion through + throws.

“0.1 + 0.2 is a coercion bug.”

Both values are already numbers. The result comes from binary floating-point precision, not type conversion.

Similar-looking conversions
CodeOperationResult
Number("")Explicit ToNumber-like conversion0
Boolean("")ToBooleanfalse
"" + 0Binary + with a string"0"
0 + ""Binary + with a string"0"

Practice: trace before you guess

5 EXERCISES
Exercise 1 · Warm-upPredict three outputs

Write the three values printed by the program, in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log([] + {});
console.log(+[]);
console.log("5" - [2]);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeTrace [] == ![]

    Use the step list in the corner-case section. What does [] == ![] evaluate to?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a string + number bug

      The current function prints 205. Change it so the total is numeric.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function totalPrice(price, shipping) {
        return price + shipping;
      }
      console.log(totalPrice("20", 5));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeImplement a tiny toNumberLike

        Write a function for primitive inputs, then compare its results with Number() for the shown cases.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function toNumberLike(value) {
          if (typeof value === "number") return value;
          if (typeof value === "string") return Number(value);
          if (typeof value === "boolean") return value ? 1 : 0;
          if (value === null) return 0;
          if (typeof value === "undefined") return NaN;
          throw new TypeError("toNumberLike only accepts simple primitives");
        }
        console.log(toNumberLike(" 0b101 "));
        console.log(toNumberLike(false));
        console.log(Number.isNaN(toNumberLike(undefined)));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeExplain why +1n throws

          In one sentence, explain the abstract operation behind the TypeError from +1n.

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which abstract operation usually runs first when an object meets +?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does [1] + [2] print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log([1] + [2]);

              Choose an answer to see the explanation.

            3. Question 3 of 7What does null + 1 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(null + 1);

              Choose an answer to see the explanation.

            4. Question 4 of 7Which value is falsy?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the baNaNa expression print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("b" + "a" + +"a" + "a");

              Choose an answer to see the explanation.

            6. Question 6 of 7What happens with +1n?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does [] == ![] print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log([] == ![]);

              Choose an answer to see the explanation.

            Key takeaways

            • Coercion is JavaScript translating values for the operation being performed.
            • Objects usually go through ToPrimitive before ToNumber or ToString.
            • ToBoolean is just the falsy list; it does not inspect object contents.
            • Binary + concatenates if either primitive is a string; otherwise it adds numerically.
            • Corner cases are easier to explain by naming the translator than by memorizing outputs.

            Remember the one-liner.
            Type coercion is JavaScript applying ToPrimitive, ToNumber, ToString, or ToBoolean because an operation needs a different kind of value.

            Up next: Equality & sameness.

            CompleteFrontend Clear concepts. Working examples.