completefrontend
Open lab My progress
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
    Name each conversion stepSay when ToPrimitive, ToNumber, ToString, and ToBoolean run.
  • 02
    Predict what + doesTell whether binary + adds numbers or joins strings.
  • 03
    Explain famous puzzlesWork through puzzles like [] == ![] one step at a time, without memorizing them.

Coercion is translation

Type coercion means JavaScript changes a value from one type to another on its own. It happens when an operator, a condition, or a built-in function needs a different kind of value than the one it got. For example, "6" - 1 needs two numbers, so JavaScript turns the text "6" into the number 6.

You met the everyday rules in the Type conversion lesson. This lesson shows the steps JavaScript follows inside, so you can explain each result yourself.

The rules come from the specification, or spec. The spec is the official rule book for JavaScript. It gives each conversion step a name. These named steps are called abstract operations. You can't call them from your code. But they run every time code like [] + {} or if (value) runs. This lesson uses four of them:

  • ToPrimitive turns an object into a plain value.
  • ToNumber turns a plain value into a number.
  • ToString turns a plain value into text. In JavaScript, text is called a string.
  • ToBoolean turns any value into true or false.
The short version

Each operator picks the conversion steps it needs. An object first becomes a plain value. Then the operator decides what it needs: a number, a string, or a simple true or false.

Real-life analogyEach operator is a counter in an office

Think of each operator as a counter in an office. Before a counter can do its job, it asks for your value in the form it accepts. The * counter only takes numbers. The if counter only wants a yes or a no. The + counter asks two questions. First it asks both sides for a plain value. Then it checks whether either one is text.

In real life: A clerk asks for one plain ID card, not your whole bag
In JavaScript: ToPrimitive asks an object for one plain value
In real life: A money desk changes your notes into the money the shop takes
In JavaScript: ToNumber or ToString changes a plain value into a number or a string
In real life: A door guard checks one short list of names
In JavaScript: ToBoolean checks the short list of falsy values

The lesson uses small, real examples. It also has a tracer: a tool that shows each conversion step for a fixed list of expressions. A later lesson, A tour of the abstract operations, reads these same steps in the spec itself.

ToPrimitive comes first

STEP THROUGH

A plain value is called a primitive. JavaScript has seven kinds of primitives:

  • strings (text), such as "hi"
  • numbers, such as 7
  • booleans: true and false
  • null and undefined
  • symbols and bigints

Everything else is an object, such as an array or {}. An operator that needs a plain value has to ask the object for one. That request is called ToPrimitive.

The request comes with a hint. The hint is a word that says which kind of plain value the operation would like: "number", "string", or "default". The word "default" means "no preference".

For an ordinary object, JavaScript calls its valueOf() and toString() methods. The hint decides which one it tries first. An object can also have its own Symbol.toPrimitive method. Then JavaScript calls that method instead and passes it the hint. The Object to primitive conversion lesson covers these three methods in detail. Here we look at what happens after the object answers.

The code below gives an object its own Symbol.toPrimitive method, so you can see which hint each line sends. Step through it.

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

This object has its own Symbol.toPrimitive method, so you can watch ToPrimitive pick a plain value before each operator carries on.

Running in
  1. script
Next: line 7
const value = {  [Symbol.toPrimitive](hint) {    return hint === "number" ? 7 : "seven";  }}; console.log(String(value));console.log(value + "!");
CallStoreChangeResultBlue line: runs next. 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.

In plain words, line by line:

  • Lines 1 to 5 make an object called value. Its method returns 7 when the hint is "number", and "seven" for any other hint.
  • Line 7 uses unary +, a plus sign in front of one value. Unary + always wants a number, so it sends "number" and gets 7.
  • Line 8 calls String(value). It sends "string" and gets "seven".
  • Line 9 uses binary +, a plus sign between two values. It does not add straight away. First it sends "default" and gets "seven". That is a string, so + joins it with "!" and prints seven!.
The value object holds a toPrimitive method. Asked with the hint "number", it gives 7. Asked with "string", it gives "seven". Asked with "default", it gives "seven".toPrimitivevalue"number""string""default"7"seven""seven"
Each operation asks the object for a plain value and says which kind it wants. That wish is the hint. +value asks with "number", String(value) with "string", and value + "!" with "default".
Real-life analogyAn object is a vending machine

An object is like a vending machine. You can't drink the machine. You press a button, and the machine drops out a drink. The operator presses a button too: the hint.

In real life: The machine is not the drink
In JavaScript: The object is not a plain value
In real life: You press the water button or the soda button
In JavaScript: The hint says which kind of plain value the operation wants
In real life: The machine must drop out a drink, or show an error
In JavaScript: ToPrimitive must give back a plain value, or throw an error

ToNumber & ToString

INTERACTIVE

Once the values are plain, most operators want either a number or a string.

  • These use ToNumber: times *, minus -, unary +, and comparisons such as <.
  • These use ToString: joining text with +, putting a value inside a template literal, and showing a value as text.
Number() needs the whole text to be a numberPop out in the code editor (opens in a new tab)JavaScript
const rawPrice = " 12 ";
const rawUnit = "12px";

console.log(Number(rawPrice));
console.log(Number(rawUnit));
console.log(String(12) + " items");

In plain words, line by line:

  • Lines 1 and 2 store two strings, the way they might arrive from a form.
  • Line 4 prints 12. Number() removes the spaces around " 12 " and reads the number.
  • Line 5 prints NaN, which means "not a number". Number() needs the whole text to be a number, and px is not. It does not keep the 12 part.
  • Line 6 uses String(12) on purpose to make the string "12". Then + joins " items", so it prints 12 items.

The two tables below show more conversions. Read each row as: when you write this, you get this, for this reason.

Some ToNumber rules: values turned into numbers
You writeYou getWhy
Number(" 0x10 ")16The spaces are removed. 0x means the digits are hexadecimal (base 16).
Number("0b101")50b means the digits are binary (base 2). 101 in binary is 5.
Number("1e3")10001e3 means 1 times 10 to the power of 3.
Number("Infinity")InfinityJavaScript knows the word Infinity as a number.
Number("")0An empty string becomes 0. Many people find this surprising.
Number("12px")NaNThe whole text must be a number. px is not, so the result is NaN.
Number(" ")0The spaces are removed. That leaves an empty string, which becomes 0.
Number(true)1true becomes 1, and false becomes 0.
Number(null)0null becomes 0.
Number(undefined)NaNundefined becomes NaN.
+1nTypeErrorUnary + runs ToNumber, and ToNumber refuses bigints.
Number(Symbol())TypeErrorA symbol refuses to become a number.
Some ToString rules: values turned into strings
You writeYou getWhy
String(-0)"0"The minus sign is lost when -0 becomes a string.
String([1, 2])"1,2"An array becomes its items, joined with commas.
String({})"[object Object]"A plain object becomes the text [object Object].
String(null)"null"null becomes the word null when you call String().
String(undefined)"undefined"undefined becomes the word undefined when you call String().
String(Symbol('id'))"Symbol(id)"Calling String() yourself works for a symbol.
"" + Symbol("id")TypeErrorJoining a symbol with + throws an error.

The tracer below shows each conversion step for a fixed list of expressions. Pick an expression with a button. Read the code on the left. Then follow the steps on the right, down to the final value.

Coercion tracer: one step at a time
The expression to followPop out in the code editor (opens in a new tab)JavaScript
console.log([] + {});
Steps"[object Object]"
1
ToPrimitive([])

The array is asked for a plain value with the hint "default". valueOf() gives back the array itself, so toString() runs. It joins the items, and there are none.

""
2
ToPrimitive({})

For a plain object, valueOf() also gives back the object itself, so toString() gives the plain value.

"[object Object]"
3
+ decision

At least one plain value is a string, so + joins strings instead of adding numbers.

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

Joining empty text adds nothing, so only the object's text is left.

"[object Object]"
Try it yourself

[] + {} gives "[object Object]". Both sides become plain values first. Then + sees a string and joins.

The tracer only knows the expressions on these buttons. It is not a full JavaScript engine. The tests run every expression in real JavaScript and check that its final value matches.
Symbols only become text when you ask by name

String(Symbol("id")) works, because you asked for a string yourself. This is an explicit conversion. But "" + Symbol("id") throws a TypeError. There JavaScript would have to convert the symbol on its own, an implicit conversion, and it refuses. This stops a symbol from turning into ordinary text by accident.

ToBoolean checks the short falsy list

A value is falsy if it counts as false in a condition, such as the one in an if. Every other value is truthy: it counts as true. ToBoolean is the simplest conversion step:

  • It does not read text to find a number in it.
  • It does not ask objects for a plain value.
  • It only checks whether the value is on the short list of falsy values.

Here is the whole falsy list. Every value that is not in this table is truthy.

Every falsy value in JavaScript
Falsy valueWhat it is
falseThe boolean false itself.
0, -0, 0nZero, minus zero, and zero as a bigint.
""An empty string. Strings such as "0" and "false" are not empty, so they are truthy.
nullA value that is empty on purpose.
undefinedA value that was never set.
NaNThe number that means "not a number".
document.allAn old exception that only exists in browsers. Know it exists, but don't rely on it.

The comments in the next block show each result. On line 4, Number("nope") gives NaN first, and NaN is falsy.

Boolean() is not the same as Number()Pop 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
Boolean() does not look inside a valuePop out in the code editor (opens in a new tab)JavaScript
console.log(Boolean("false"));
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean(NaN));

In plain words, line by line:

  • Line 1 prints true. "false" is a string that is not empty, so it is truthy. The word inside does not matter.
  • Lines 2 and 3 also print true. An empty array and an empty object are still objects, and every object is truthy. ToBoolean does not count what is inside them.
  • Line 4 prints false, because NaN is on the falsy list.
Four values given to Boolean(). The text "false" gives true. An empty array gives true. An empty object gives true. NaN gives false, because NaN is on the falsy list."false"[]{}NaNtruetruetruefalsenot emptyan objectan objecton the list
Boolean() does not read what is inside a value. "false" is text that is not empty. [] and {} are objects, and every object counts as true. Only NaN is on the short falsy list.

To check whether an array has items, write items.length > 0. Don't use truthiness for that.

+ with strings: add or join?

STEP THROUGH

Binary +, the plus sign between two values, causes the most famous coercion surprises. It can do two jobs: add numbers or join strings. It picks a job in four steps:

  1. Work out the value on the left, then the value on the right.
  2. If either value is an object, turn it into a plain value with ToPrimitive and the hint "default".
  3. If either plain value is a string, turn both into strings and join them.
  4. Otherwise, turn both into numbers and add them.
Minus and times always do mathPop out in the code editor (opens in a new tab)JavaScript
console.log("6" - 1);
console.log("6" * "2");
console.log("6" + 1);

In plain words, line by line:

  • Line 1 prints 5. Minus can only do math, so JavaScript turns "6" into the number 6.
  • Line 2 does the same for times and prints 12.
  • Line 3 is different. Binary + can join strings, and "6" is a string. So JavaScript turns 1 into the string "1" and prints 61.

So changing - to + can change both the job and the type of the result. The next example has more than one + in a row. Step through it and switch between the three expressions.

Each + decides on its own, left to right
Step 0 of 3Ready
Your turn: follow the blue line

JavaScript works from left to right. Each + decides what to do using the two values it has at that moment.

Running in
  1. script
Next: line 1
console.log(result);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick an expression to step through

Picking another expression starts the steps again, so you can guess the result first.

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.

Each + makes its own choice, one at a time, from left to right. That is why 1 + 2 + "3" gives "33", but "1" + 2 + 3 gives "123".

Left: 1 + 2 + "3". First 1 + 2 adds and gives 3. Then 3 + "3" joins text and gives "33". Right: "1" + 2 + 3. First "1" + 2 joins text and gives "12". Then "12" + 3 joins text and gives "123".1 + 2 + "3"1 + 2adds33 + "3"joins"33""1" + 2 + 3"1" + 2joins"12""12" + 3joins"123"
JavaScript works from left to right, one + at a time. Each + looks only at its own two values. On the left, the first + sees two numbers and adds. On the right, text comes first, so every + joins text.
Adds or joins?
  • true + 1
  • 1 + 2
  • null + 5
  • [] + []
  • "" + 0
  • ({ toString() { return "4"; } }) + 1
Try it yourself
0 of 6 correct

Sort each + expression by what the + does in the end: add numbers or join strings. 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, step by step

Coercion puzzles look random if you try to memorize the answers. They make sense once you name each conversion step. The table below shows famous puzzles. Read each row as: this code gives this result, and these are the steps. The tests for this lesson run every one of them.

Famous coercion puzzles and their steps
You writeYou getThe steps
[] + []""Both arrays become empty strings. Then + joins two empty strings.
[] + {}"[object Object]"[] becomes "". {} becomes "[object Object]". Then + joins them.
({}) + []"[object Object]"The round brackets make {} an object. Without them, some consoles read {} at the start of a line as an empty block of code.
"b" + "a" + +"a" + "a""baNaNa"+"a" gives NaN. Then + turns NaN into the string "NaN" and joins it with the other strings.
[] == ![]true![] is false. Then == turns false into 0, and [] into "" and then 0. The next lesson covers ==.
undefined + 1NaNundefined becomes NaN, and math with NaN always gives NaN.
0.1 + 0.20.30000000000000004Nothing is converted here, because both are numbers. The tiny error comes from how numbers are stored (see the Numbers lesson).
[] + {}. The empty array becomes the empty text "". The empty object becomes the text "[object Object]". Then + joins the two texts, and the result is "[object Object]".[]+{}becomes textbecomes text""+"[object Object]""[object Object]"joins text
First each object becomes a plain value. The empty array becomes empty text, "". The empty object becomes "[object Object]". Now one side is text, so + joins the two.

The next block makes two objects that look the same and compares them three ways.

Two objects that look the same are not equalPop out in the code editor (opens in a new tab)JavaScript
const firstCart = { total: 5 };
const secondCart = { total: 5 };

console.log(firstCart === secondCart);
console.log(firstCart == secondCart);
console.log(firstCart == "[object Object]");

In plain words, line by line:

  • Lines 1 and 2 each make a new object. They look the same, but they are two separate objects.
  • Lines 4 and 5 print false. Comparing two objects checks whether they are the same object. It does not compare what is inside them.
  • Line 6 prints true. Loose equality, ==, turns the object into its usual string, "[object Object]", before it compares.

Line 6 is a puzzle to explain, not a style to copy. In real code, compare the fields you care about, or use strict equality, ===. The Equality & sameness lesson covers == in full.

The next block shows [] == ![] getting simpler, one step per line. It is a trace to read, not code to run.

[] == ![], one step per lineJavaScript
[] == ![]
[] == false      // ![] is false because arrays are truthy
[] == 0          // false becomes 0
"" == 0          // [] becomes "" through ToPrimitive
0 == 0           // "" becomes 0
true
{} at the start of a line can be a block

Inside an expression, ({}) + [] is the string "[object Object]". But some consoles read a whole line that starts with {} differently. If you type {} + [], the first {} may be read as an empty block of code, not an object. Only +[] is left, and that gives 0. Round brackets around {} make it an object again.

Use coercion on purpose in real code

Good code doesn't hide coercion. It makes every conversion easy to see. Convert data at the place where it comes into your program. Then the rest of your code works with the types it expects.

Turn form text into numbers 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;
}

In plain words, line by line:

  • A form field always gives you a string, even when the person typed digits.
  • Lines 2 and 3 turn the two strings into numbers with Number().
  • Lines 5 to 7 stop early with a message if either one is NaN. Number.isNaN() returns true only for NaN.
  • Line 9 adds two real numbers, so + does math.
Turn text into a number, then check itPop out in the code editor (opens in a new tab)JavaScript
function readQuantity(value) {
  const quantity = Number(value);
  return Number.isNaN(quantity) ? null : quantity;
}

console.log(readQuantity("3"));
console.log(readQuantity("3 items"));

Line 6 prints 3. Line 7 prints null, because Number("3 items") is NaN.

Form fields, the query part of a web address, and JSON data from a server often give you strings. Convert them once, on purpose, with Number(), String(), or Boolean(). Then reject NaN before it reaches later math. Don't let + guess whether a checkout total is math or text.

Common misconceptions

“Coercion is random.”

It follows written rules in the spec. The results can surprise you, but the same four conversion steps explain them.

“ToBoolean uses valueOf or toString on objects.”

No. Ordinary objects are always truthy. ToBoolean never asks an object for a plain value.

“+ always turns both sides into numbers and adds.”

Binary + only adds when neither plain value is a string. If one side is a string, it joins strings.

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

String() can turn a symbol into a string. Joining a symbol with + throws a TypeError.

“0.1 + 0.2 is a coercion bug.”

Both values are already numbers, so nothing is converted. The tiny error comes from how computers store decimal numbers.

The four lines in the next table look alike, but each one runs a different conversion. Read each row as: this code runs this step and gives this result.

Code that looks alike, with different conversions
You writeWhat runsYou get
Number("")Number() turns the empty string into a number.0
Boolean("")ToBoolean finds the empty string on the falsy list.false
"" + 0Binary + sees a string, so it joins."0"
0 + ""Binary + sees a string, so it joins."0"

Practice: work out each step before you answer

5 EXERCISES
Exercise 1 · Warm-upPredict three outputs

Write the three values the program prints, 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 · PracticeWork out [] == ![]

    Use the step-by-step block in the corner cases section. What does [] == ![] give?

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

      Exercise 3 · PracticeFix a string + number bug

      The function prints 205 now. Change it so the total is a number.

      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 · ChallengeWrite a small toNumberLike()

        Write a function for plain values. Then compare its results with Number() for the cases shown.

        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 which conversion step makes +1n throw a TypeError.

          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 conversion step 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 line 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 changing a value's type because an operation needs a different kind of value.
            • Objects usually go through ToPrimitive first. Then comes ToNumber or ToString.
            • ToBoolean only checks the short falsy list. It never looks inside an object.
            • Binary + joins strings if either plain value is a string. Otherwise it adds numbers.
            • Puzzles are easier to explain when you name each conversion step than when you memorize the answers.

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

            Up next: Equality & sameness.

            CompleteFrontend Clear concepts. Working examples.