completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Object to primitive conversion

Learn what happens when an object meets + or a template literal, which hint JavaScript sends, and how Symbol.toPrimitive, valueOf, and toString control the answer.

By the end, you can
  • 01
    Predict the hintSay which hint a piece of code sends to an object: string, number, or default.
  • 02
    Follow the orderExplain when JavaScript calls Symbol.toPrimitive, valueOf, and toString, and in which order.
  • 03
    Build friendly objectsAdd conversion methods so your objects work as you mean them to in logs, math, and template literals.

When an object must become a simple value

In the Type conversion lesson, you saw JavaScript turn values into strings and numbers. You also saw two puzzles. [] + [] gives an empty string. [] + gives the text "[object Object]". This lesson explains the step that was missing.

When JavaScript needs a simple value but has an object, it asks the object to give back one simple value.

A simple value is called a primitive. These are the primitives:

  • a string, like "hi"
  • a number, like 5
  • a boolean: true or false
  • a bigint, like 10n
  • a symbol, a special unique key
  • null and undefined

An object is different. It is a container that holds many named values. Some code can't work with a whole container. Here are four examples:

  • +obj: a plus with nothing on its left. It turns a value into a number.
  • obj * 2: multiplication.
  • `$${obj}`: a template literal. It is text inside backticks, and ${...} puts a value into the text.
  • obj == 1: loose equality, with two equals signs. It converts values before it compares them.

Each of these first asks the object for a primitive. Then it carries on with that primitive.

The main idea

Object to primitive conversion is how JavaScript asks an object for one primitive. It happens before JavaScript turns the value into a string or a number, compares it, or uses it with +.

Start with a small example. The user object below has a toString() method. A toString() method returns the object as text.

A user object that turns into its namePop out in the code editor (opens in a new tab)JavaScript
const user = {  name: "Asha",  toString() {    return this.name;  }}; console.log(String(user));console.log(`${user}`);

Line 8 prints Asha. String(user) asks the object for text, so JavaScript calls toString(). Line 9 prints Asha too, because the template literal also asks for text. The object didn't change. Only the code around it changed how JavaScript asked.

When JavaScript asks an object for a primitive, it also says what kind of answer it wants. This word is called the hint. There are three hints: "string", "number", and "default".

Real-life analogyFilling in a form

Someone hands you a form. A field called "Name" wants text, so you write a name. A field called "Age" wants a number, so you write a number. A field called "Anything" doesn't say, so you write what seems most useful. The field's label is like the hint.

In real life: A field asks for text
In JavaScript: The string hint
In real life: A field asks for a number
In JavaScript: The number hint
In real life: A field says Anything
In JavaScript: The default hint
In real life: You write one answer
In JavaScript: The object returns one primitive

Two later lessons, Type coercion and Equality & sameness, use these rules inside bigger steps. Much later, A tour of the abstract operations shows the names that the specification, the official rulebook for JavaScript, gives them: ToPrimitive and OrdinaryToPrimitive. For now, you only need the rules in this lesson. You can run them and predict them.

The order JavaScript follows

CORE RULE

JavaScript doesn't pick a method at random. Each time an object must become a primitive, it follows these steps in order:

  1. Does the object have a method at the key Symbol.toPrimitive? If yes, JavaScript calls it and passes the hint: "string", "number", or "default".
  2. If that method returns a primitive, JavaScript uses it. If it returns an object, JavaScript throws a TypeError. A TypeError is an error that means "this value is the wrong kind".
  3. If there is no such method and the hint is "string", JavaScript tries toString() first, then valueOf(). It uses the first primitive it gets.
  4. If there is no such method and the hint is "number" or "default", JavaScript tries valueOf() first, then toString(). It uses the first primitive it gets.
  5. If none of these gives a primitive, JavaScript throws a TypeError.
One exception: dates

Date objects come with their own built-in Symbol.toPrimitive method. It treats the "default" hint like "string". You'll see this in the section on arrays and dates.

The method at Symbol.toPrimitive must return a primitive. The example below breaks this rule on purpose, so you can spot the error if you meet it later.

The custom method must return a primitivePop out in the code editor (opens in a new tab)JavaScript
const box = {  [Symbol.toPrimitive]() {    return {};  }}; try {  String(box);} catch (error) {  console.log(error.name);}
  • Line 3 returns {}, an empty object.
  • Line 8 asks for a string, so JavaScript calls the method and gets an object back.
  • JavaScript doesn't go on to try valueOf() or toString(). It throws a TypeError right away.
  • The catch block on line 9 catches the error. Line 10 prints its name: TypeError.

This is the full error message from a real run of the same code: TypeError: Cannot convert object to primitive value.

Symbol.toPrimitive: the object's own answer

STEP THROUGH

A symbol is a special, unique kind of key. Symbol.toPrimitive is a symbol that JavaScript itself knows about. That is why it is called a well-known symbol. When you put a method at this key, the object gives its own answer each time JavaScript asks it for a primitive.

You write the key in square brackets, like [Symbol.toPrimitive](hint) { ... }. The method gets one argument, hint. It is the kind of answer JavaScript wants.

Real-life analogyA custom answer box on the form

Think of the form again. The usual fields are toString() and valueOf(). Symbol.toPrimitive is a custom answer box at the top of the form. JavaScript reads it first.

In real life: The form has a custom answer box
In JavaScript: An object with [Symbol.toPrimitive]
In real life: The field asks for text or a number
In JavaScript: The hint argument
In real life: The box gives one answer
In JavaScript: The primitive the method returns

The price object below has a method at Symbol.toPrimitive. Step through it, and watch the same object give different answers for different hints.

A price object with its own answer for each hint
Step 0 of 4Ready
Your turn: follow the blue line

With a method at Symbol.toPrimitive, one object can give text for one hint and a number for another.

Running in
  1. script
Next: line 1
  cents: 1299,  [Symbol.toPrimitive](hint) {    if (hint === "string") return "₹12.99";    return this.cents / 100;  }}; String(price);+price;price + 1;
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.
  • Line 4: if the hint is "string", the method returns the text "₹12.99".
  • Line 5: for any other hint, it returns this.cents / 100, which is the number 12.99.
  • Line 9 asks with "string". Line 10 asks with "number". Line 11 asks with "default", so price + 1 is 13.99.
A cart called price holds cents: 1299. One arrow gives the text ₹12.99 for the string hint. Another arrow gives the number 12.99 for the number or default hint.cents: 1299price"₹12.99"string hint12.99number ordefault hint
One price object, two answers. When JavaScript asks for text, it gets "₹12.99". When it asks for a number, or doesn't say, it gets 12.99.

This is powerful, so use it rarely. A money object that works in sums can be handy. A user object that quietly turns into a number is confusing. If the result would surprise someone reading the code, use a named method instead, like price.toNumber().

valueOf and toString: the backup methods

INTERACTIVE

Most objects don't have a method at Symbol.toPrimitive. Then JavaScript uses two older methods instead: valueOf() and toString(). This lesson calls them the backup methods. Every plain object already has both. It borrows them from Object.prototype, a built-in object that all plain objects share methods with.

Their names tell you their jobs:

  • toString() should return the object as text.
  • valueOf() should return the simple value inside the object, if it has one.

The table shows which backup method JavaScript tries first for each hint. Read each row as: "for this hint, try this method, then that one."

Which backup method JavaScript tries first
The hintTried firstTried secondWhere you see it
stringtoString()valueOf()String(obj) and template literals want text.
numbervalueOf()toString()+obj, obj * 2, and obj > 0 want a number.
defaultvalueOf()toString()obj + 1 and obj == 1 ask without saying which kind they want.
Left: for the string hint, JavaScript tries toString first, then valueOf if there is no primitive. Right: for the number or default hint, it tries valueOf first, then toString.string1. toString()no primitive?2. valueOf()number, default1. valueOf()no primitive?2. toString()
The hint decides which backup method JavaScript tries first. If the first one doesn't give a primitive, JavaScript tries the second one. The first primitive it gets is the answer.

Now you can explain "[object Object]". A plain object's valueOf() returns the object itself. That is not a primitive, so JavaScript moves on to toString(). A plain object's toString() returns the text "[object Object]". Arrays have their own toString(), so they give different results. You'll see them later in this lesson.

You don't have to use these automatic conversions. When a value could mean more than one thing, named methods are easier to read and to test. In the example below, toFixed(2) turns a number into text with two digits after the dot.

Named methods show which answer you wantPop out in the code editor (opens in a new tab)JavaScript
const price = {  cents: 500,  toLabel() {    return `$${(this.cents / 100).toFixed(2)}`;  },  toNumber() {    return this.cents / 100;  }}; console.log(price.toLabel());console.log(price.toNumber() + 2);

Line 11 prints $5.00. Line 12 prints 7, because 5 + 2 is 7. The code calls toLabel() when it wants text, and toNumber() when it wants a number. A reader doesn't need to remember which code sends which hint.

Now try the backup methods yourself. Pick which methods the object has. Then step through three conversions and watch which method JavaScript calls. The calls array keeps a list of every method that ran.

Which backup method runs?
Step 0 of 5Ready
Your turn: follow the blue line

Pick which methods the object has. Then step through and see which backup method runs for the string, number, and default hints.

Running in
  1. script
Next: line 1
const sample = {  valueOf() {    calls.push("valueOf");    return 42;  },  toString() {    calls.push("toString");    return "forty-two";  }}; String(sample);+sample;sample + "!";
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick which methods the object has, then step through the same three lines

When you pick a different object, the steps start again with an empty list of calls.

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 three hints: string, number, and default

INTERACTIVE

The hint says what kind of answer would help. It doesn't force the final type. The object can return any primitive. After that, the code may convert the primitive again. For example, obj * 2 asks with the "number" hint. If the object returns the text "5", multiplication still turns it into the number 5.

The table lists common code and the hint each one sends. Read each row as: "this code sends this hint, for this reason."

Which hint each piece of code sends
When you writeThe hint sentWhy
String(obj)stringYou asked for a string directly.
A template, ${obj}stringA template literal builds text.
[obj].join()stringjoin() turns each item in the array into text.
+objnumberA plus with nothing on its left only turns a value into a number.
obj * 2numberMultiplication only works with numbers.
obj > 0numberComparing with > or < asks objects for numbers first.
obj + 1defaultA plus between two values can add numbers or join text, so it doesn't say which it wants.
obj == 1defaultLoose equality asks the object for a primitive when the other side is a primitive.
Three pieces of code, each with an arrow to a tag. String(obj) sends the hint string. +obj sends the hint number. obj + 1 sends the hint default.String(obj)stringwants text+objnumberwants a numberobj + 1defaulthas not decided
Each piece of code sends one hint to the object. String(obj) wants text, +obj wants a number, and obj + 1 doesn't say yet.

Try the code yourself. The object below prints the hint it gets. Then it returns a primitive that makes the result easy to read:

  • "object-card" for the "string" hint
  • 7 for the "number" hint
  • 3 for the "default" hint
Which hint did the object get?
An object that prints the hint it getsPop out in the code editor (opens in a new tab)JavaScript
const obj = {  [Symbol.toPrimitive](hint) {    console.log("hint:", hint);    if (hint === "string") return "object-card";    if (hint === "number") return 7;    return 3;  }}; String(obj);`${obj}`;obj + "";+obj;obj * 2;obj + 1;obj == 1;obj > 0;[obj].join();
Real runString(obj)
Hint the object gotstring
Result"object-card"
Printedhint: string
Try it yourself

You picked String(obj). The object got the string hint, and the result is "object-card".

Each result comes from a real run of the object above. Pick one of the listed pieces of code to run it.
Which hint?
  • String(obj)
  • `${obj}`
  • [obj].join()
  • +obj
  • obj * 2
  • obj > 0
  • obj + 1
  • obj == 1
Try it yourself
0 of 8 correct

Sort each piece of code by the hint it sends to the object.

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

Arrays, objects, and dates explain the old puzzles

REAL OUTPUTS

Arrays are objects too. An array's toString() works like join(","). It puts the items together into one piece of text, with a comma between them. This one fact explains many JavaScript puzzles.

Arrays and objects turned into primitivesPop out in the code editor (opens in a new tab)JavaScript
String({});String([]);[1, 2] + "";[5] * 2;[1, 2] + [3];[] + {};({} + []);
A date turned into text and a numberPop out in the code editor (opens in a new tab)JavaScript
const date = new Date("2020-01-02T00:00:00.000Z");String(date);+date;date + 1;

In the arrays example, line by line:

  • Line 1: String({}) is "[object Object]", from a plain object's toString().
  • Line 2: String([]) is an empty string. An empty array has no items to join.
  • Line 3: [1, 2] + "" becomes "1,2".
  • Line 4: [5] * 2 first turns the array into "5". Multiplication then turns "5" into 5, so the result is 10.
  • Line 5: [1, 2] + [3] becomes "1,2" + "3". Both sides are text, so + joins them into "1,23".
  • Line 6: [] + {} becomes "" + "[object Object]", which is "[object Object]".
  • Line 7: ({} + []) is also "[object Object]". The round brackets matter here. In the console, a line that starts with {} can be read as an empty block of code, not an object. Then only +[] is left, which is 0. The brackets make JavaScript read {} as an object.
Top: the arrays [1, 2] and [3]. Arrows down show each array turning into text: 1,2 and 3. An arrow to the right shows plus joining the text into 1,23.[1, 2]+[3]each array becomes text"1,2"+"3""1,23"joined text
First, each array turns into text by joining its items with commas. Then + sees two strings, so it joins them: "1,2" + "3" is "1,23".

Dates are different. They come with their own Symbol.toPrimitive method. In the date example, line by line:

  • Line 2: String(date) gives the date as readable text.
  • Line 3: +date asks with the "number" hint. It gives a timestamp: the number of milliseconds since 1 January 1970.
  • Line 4: date + 1 asks with the "default" hint. A date treats "default" like "string", so it returns the date as text. Then + adds "1" to the end of that text.

Where you’ll use this

Most objects in an app don't need their own conversion. Named methods are usually clearer. But knowing how conversion works helps in three places:

  1. Readable logs and messages. A good toString() means your logs show useful text, not "[object Object]".
  2. Small value objects. A value object is a small object that stands for one value, such as an amount of money, a length, a temperature, or an ID. Some of these can have a simple, well-tested conversion.
  3. Finding bugs. When +, ==, or > gives a strange result, ask "which hint did this send?" instead of guessing.

A conversion method is your own code, and JavaScript may call it when you don't expect it. For example, logging and template literals call it. So keep these methods short and safe:

  • They should only build and return a value.
  • They shouldn't change anything, such as the items in a cart.
  • They shouldn't send requests over the network.
  • They should give the same answer every time they are called.
Rule of thumb

Use named methods for your app's main logic. Use automatic conversion only where the meaning is obvious: log messages, labels, and tiny value objects.

A total shown on screen is a good place for automatic conversion. In the example below, the total turns into readable text inside a message. The number of cents stays in its own property, ready for math and for saving.

A cart total shown in a messagePop out in the code editor (opens in a new tab)JavaScript
const cartTotal = {  cents: 1299,  toString() {    return `$${(this.cents / 100).toFixed(2)}`;  }}; console.log(`Total: ${cartTotal}`);console.log(cartTotal.cents / 100);

Line 8 prints Total: $12.99. Line 9 prints 12.99. The toString() method only builds the label. The math on line 9 uses the cents property directly.

Not every piece of code asks for a primitive. This explains why a conversion method sometimes seems to be skipped. Here are two examples:

  • Truthiness is whether a value counts as true in a check like if. Every object counts as true. JavaScript doesn't ask the object for a primitive to decide this.
  • JSON.stringify() turns an object into JSON text. It reads the object's own properties. It doesn't call toString().
Boolean checks the object itselfPop out in the code editor (opens in a new tab)JavaScript
const amount = {  valueOf() {    return 0;  }}; console.log(Boolean(amount));console.log(amount == 0);
JSON reads the object's propertiesPop out in the code editor (opens in a new tab)JavaScript
const user = {  name: "Asha",  toString() {    return "not used by JSON";  }}; console.log(JSON.stringify(user));
  • Boolean example, line 7: Boolean(amount) prints true, because every object counts as true.
  • Boolean example, line 8: amount == 0 asks the object for a primitive. valueOf() returns 0, and 0 equals 0, so it prints true.
  • JSON example, line 8: it prints {"name":"Asha"}. JSON.stringify() read the name property and didn't call toString().

Common misconceptions

  • "A plus sign asks for a string." Not at first. With an object, + sends the "default" hint. Only when both sides are primitives does it choose: if either side is text, it joins them; if not, it adds them.
  • "The hint decides the final type." No. The object can return any primitive. The code may convert that primitive again afterward.
  • "valueOf() always runs first." Only for the "number" and "default" hints. The "string" hint tries toString() first.
  • "Plain objects can't become strings." They can. Their built-in toString() returns "[object Object]".
  • "Dates work like plain objects." No. A date treats the "default" hint like "string". That is why date + 1 adds "1" to the end of the date text.
  • "If Symbol.toPrimitive returns an object, JavaScript tries valueOf() next." No. It throws a TypeError. The method must return a primitive.
  • "A conversion method can do any work." It shouldn't. It should only return a value. Don't change data inside it, such as the items in a cart.
  • "Every value object needs Symbol.toPrimitive." No. Start with named methods. Add automatic conversion only when the meaning is obvious and won't change.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upName the hint

Which hint does JavaScript send to the object in obj + 1?

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

    Exercise 2 · PracticeGive a price its own answer with Symbol.toPrimitive

    Add a method so that String(price) is "₹25.00" and price + 5 prints 30. Then type the second printed value.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const price = {
      cents: 2500,
      // add Symbol.toPrimitive here
    };
    console.log(String(price));
    console.log(price + 5);

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

      Exercise 3 · PracticePredict two array results

      Predict what both lines print. Type the first result, then the second, in the answer box.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      console.log([5] * 2);
      console.log([1, 2] + [3]);

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

        Exercise 4 · PracticeFix [object Object] in a template

        The starter code prints Hello, [object Object]!. Add a method so the template prints the user's name.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const user = { name: "Ada" };
        console.log(`Hello, ${user}!`);
          Exercise 5 · ChallengeExplain date + 1

          In a few words: does date + 1 join text (string concatenation) or add numbers (numeric addition)?

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

            Check your understanding

            8 QUESTIONS
            Object to primitive quiz · 8 questionsScore: first tries count
            1. Question 1 of 8An object has a method at Symbol.toPrimitive. What does JavaScript do first?

              Choose an answer to see the explanation.

            2. Question 2 of 8What does this object plus 1 print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const obj = { valueOf() { return 4; } };
              console.log(obj + 1);

              Choose an answer to see the explanation.

            3. Question 3 of 8Which piece of code sends the string hint?

              Choose an answer to see the explanation.

            4. Question 4 of 8What does this array times 2 print?

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

              Choose an answer to see the explanation.

            5. Question 5 of 8What does this array plus another array print?

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

              Choose an answer to see the explanation.

            6. Question 6 of 8What happens if the method at Symbol.toPrimitive returns {}?

              Choose an answer to see the explanation.

            7. Question 7 of 8Why is date + 1 usually text, while +date is a timestamp?

              Choose an answer to see the explanation.

            8. Question 8 of 8What does this line print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const amount = { valueOf() { return 0; } };
              console.log(Boolean(amount), amount == 0);

              Choose an answer to see the explanation.

            Key takeaways

            • JavaScript checks for a method at Symbol.toPrimitive first, and passes it the hint.
            • If that method returns an object, JavaScript throws a TypeError.
            • Without that method, the "string" hint tries toString() before valueOf().
            • Without that method, the "number" and "default" hints try valueOf() before toString().
            • A plus between two values and == send "default". +obj, *, and > send "number". Template literals and String() send "string".
            • Arrays turn into text by joining their items with commas. Dates treat the "default" hint like "string".

            Object to primitive conversion is JavaScript asking an object for one primitive, using a hint, before the rest of the code goes on.

            Up next: Array basics.

            CompleteFrontend Clear concepts. Working examples.