cf.completefrontendCode editorOpen lab
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 hintKnow when JavaScript asks an object for string, number, or default.
  • 02
    Trace the fallback orderExplain when Symbol.toPrimitive, valueOf, and toString run.
  • 03
    Design friendly objectsAdd conversions that make logging, math, and templates behave on purpose.

Objects meet primitive-only places

You already know from the Type conversion lesson that JavaScript can turn values into strings and numbers. You also saw puzzles such as [] + [] becoming an empty string and [] + becoming "[object Object]". This lesson explains the missing middle step: when an object is used where JavaScript needs a primitive, JavaScript asks the object to hand over one primitive value.

A primitive is a value such as a string, number, boolean, bigint, symbol, null, or undefined. Objects are containers. Operators like unary +, multiplication, template literal interpolation, loose equality, and many string APIs cannot work directly with the whole container, so they request a primitive first.

Working definition

Object to primitive conversion is JavaScript’s process for asking an object for one primitive answer before continuing with string conversion, numeric conversion, comparison, or +.

Real-life analogyThe hint asks what kind of answer is needed

Imagine asking a person to fill in a form. If the field says “Name”, they write text. If it says “Age”, they write a number. If it says “Anything”, they choose what seems most useful. JavaScript sends a similar label, called a hint, when it asks an object for a primitive.

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 asks for a useful answer
In JavaScript: The default hint
In real life: You fill in one answer
In JavaScript: The object returns one primitive

Where the analogy stops: A person can explain why the form is confusing. JavaScript only follows the conversion method and throws if it does not receive a primitive.

The deeper Type coercion and Equality & sameness lessons will revisit these rules inside larger algorithms. Much later, A tour of the abstract operations names the spec steps ToPrimitive and OrdinaryToPrimitive. For now, you only need the practical rule you can run and predict.

The conversion algorithm

CORE RULE

JavaScript does not randomly pick between methods. It follows this order every time an object needs to become a primitive:

  1. If obj[Symbol.toPrimitive] exists, call it with one argument: the hint "string", "number", or "default".
  2. If that method returns a primitive, use it. If it returns an object, throw TypeError.
  3. If there is no symbol method and the hint is "string", try toString(), then valueOf(). Use the first primitive result.
  4. If there is no symbol method and the hint is "number" or "default", try valueOf(), then toString(). Use the first primitive result.
  5. If none of those calls produces a primitive, throw TypeError.
Accuracy note

Date is the famous exception in everyday code: for the "default" hint it behaves string-like. You will see that in the arrays and dates section.

Here is the TypeError part proved by the same runtime that executes the tests for this lesson: a custom hook that returns an object produces TypeError: Cannot convert object to primitive value.

Symbol.toPrimitive: the custom answer box

STEP THROUGH

Symbol.toPrimitive is a well-known symbol: a symbol JavaScript itself looks for to customize built-in behavior. Put a method at that key and the object carries its own conversion answer sheet.

Real-life analogyA custom answer box on the form

The older fallback methods are the usual form fields. Symbol.toPrimitive is a custom answer box that JavaScript reads first.

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

Where the analogy stops: A real sheet might contain several answers at once. JavaScript needs exactly one primitive returned for this single conversion.

The money object below returns a formatted label for text, and a number for math. Step through it and notice that the same object can answer differently depending on the hint.

A money object with a custom primitive answer
Step 0 of 4Ready
Your turn: follow the blue line

A custom Symbol.toPrimitive method lets one object answer differently for text and math.

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
  cents: 1299,  [Symbol.toPrimitive](hint) {    if (hint === "string") return "₹12.99";    return this.cents / 100;  }}; String(price);+price;price + 1;
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.

This is powerful, so use it sparingly. A money object that behaves numerically in totals can be handy. A user profile object that secretly becomes a number is confusing. If the conversion surprises a teammate, prefer an explicit method such as price.toNumber().

valueOf & toString: the fallback answers

INTERACTIVE

Most objects do not define Symbol.toPrimitive. Then JavaScript uses the older methods valueOf() and toString(). Their names describe their intended jobs:

  • toString() should return a text representation.
  • valueOf() should return the underlying primitive value, when the object has one.
Fallback order for ordinary object conversion
HintFirst methodSecond methodEveryday example
stringtoString()valueOf()String(obj) and template literals want text.
numbervalueOf()toString()Unary +obj, obj * 2, and relational comparison want numbers.
defaultvalueOf()toString()Binary + and loose equality ask before deciding what to do.

Plain objects inherit valueOf(), but it returns the object itself. That is not a primitive, so JavaScript keeps going and often lands on toString(), whose inherited result is "[object Object]". Arrays override toString(), which is why arrays have their own surprises.

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

Pick an object shape, then step through the real fallback order for string, number, and default conversions.

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
const sample = {  valueOf() {    calls.push("valueOf");    return 42;  },  toString() {    calls.push("toString");    return "forty-two";  }}; String(sample);+sample;sample + "!";
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Change the object, then step through the same conversions

Changing the object starts the replay again, so the call log is fresh.

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 string, number & default hints

INTERACTIVE

The hint is not the final type guarantee. It is JavaScript saying what kind of answer would be useful. After an object returns a primitive, the surrounding operation may still convert that primitive again. For example, multiplication asks for a number-like primitive, then converts it to a number for math.

Common operations and the hint they send
OperationHintWhy
String(obj)stringExplicit string conversion.
Template ${obj}stringInterpolation creates text.
[obj].join()stringJoining array elements converts each element to text.
+objnumberUnary plus only means numeric conversion.
obj * 2numberMultiplication only does numeric math.
obj > 0numberRelational comparison converts objects before comparing.
obj + 1defaultBinary plus may add or concatenate, so it asks neutrally first.
obj == 1defaultLoose equality converts an object when the other side is primitive.

Try the operations yourself. The object logs the real hint it received, then returns a primitive chosen to make the result obvious.

Hint explorer: what did JavaScript ask for?
Object that logs its hintPop 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 receivedstring
Result"object-card"
Loghint: string
Try it yourself

The selected operation is String(obj). The object logged the string hint and produced "object-card".

Every result is produced by a real object with Symbol.toPrimitive; the playground only lets you choose from the visible operations.
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 operation by the hint it sends to an object.

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

Arrays, objects & dates explain the classic puzzles

REAL OUTPUTS

Arrays are objects, but their toString() method behaves like join(","). That one fact explains many early JavaScript surprises.

Arrays and object expression formsPop out in the code editor (opens in a new tab)JavaScript
String({});String([]);[1, 2] + "";[5] * 2;[1, 2] + [3];[] + {};({} + []);
Date's special defaultPop 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;
  • String([]) is an empty string because joining no elements gives empty text.
  • [1, 2] + "" becomes "1,2".
  • [5] * 2 becomes "5" * 2, then numeric multiplication gives 10.
  • [1, 2] + [3] becomes "1,2" + "3", which is "1,23".
  • [] + as an expression becomes "[object Object]".
  • ( + []) as an expression also becomes "[object Object]". In a console, + [] typed at the beginning of a statement can parse as a block, so the remaining +[] is 0. The parentheses prove the expression form.

Dates are special. String(date) gives a readable date string. +date asks for the number hint and gives the timestamp. But date + 1 uses the default hint, and Date treats default like string, so the 1 is appended to a date string.

Where you’ll use this

You do not need custom conversion on every object. Most application objects are clearer with named methods. But understanding conversion helps in three practical places:

  1. Readable logging and templates. A useful toString() keeps debugging output from showing "[object Object]".
  2. Value objects. Money, measurements, temperatures, and IDs sometimes deserve a small, well-tested primitive conversion.
  3. Debugging coercion. When +, ==, or comparison surprises you, you can ask “which hint did this send?” instead of guessing.
Rule of thumb

Prefer explicit methods for business logic. Use primitive conversion to make objects pleasant at the language boundary: logs, labels, arithmetic wrappers, and tiny value objects.

Common misconceptions

  • “Binary plus asks for string.” Not first. With objects it asks for the default hint, then decides between addition and concatenation after primitives are available.
  • “The hint forces the final type.” No. The object can return any primitive. The operation may convert again afterward.
  • “valueOf always wins.” Only for number and default ordinary fallback. The string hint tries toString() first.
  • “Plain objects cannot become strings.” They can: inherited toString() returns "[object Object]".
  • “Dates behave like ordinary objects.” Date treats the default hint as string-like, which is why date + 1 concatenates.
  • “Returning an object from Symbol.toPrimitive falls back.” It throws TypeError. The custom hook must return a primitive.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upName the hint

What hint does JavaScript send to an object in obj + 1?

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

    Exercise 2 · PracticeImplement Symbol.toPrimitive for a price

    Add a conversion method so String(price) is "₹25.00" and price + 5 prints 30. 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 conversions

      Predict both lines. Put the first output and second output 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 one phrase, explain whether date + 1 is string concatenation or numeric addition.

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

            Check your understanding

            7 QUESTIONS
            Object to primitive quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What happens first when an object has Symbol.toPrimitive?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this object plus expression 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 7Which operation sends the string hint?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this array multiplication 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 7What does this array plus expression 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 7What if Symbol.toPrimitive returns {}?

              Choose an answer to see the explanation.

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

              Choose an answer to see the explanation.

            Key takeaways

            • Symbol.toPrimitive is checked first and receives the hint.
            • If the custom hook returns an object, JavaScript throws TypeError.
            • Without the symbol hook, string tries toString() before valueOf().
            • Without the symbol hook, number and default try valueOf() before toString().
            • Binary + and loose equality use the default hint; unary plus, multiplication, and relational comparison use number; templates and String() use string.
            • Arrays stringify by joining, and Date treats the default hint as string-like.

            Object to primitive conversion is JavaScript asking an object for one primitive value, guided by a hint, before the surrounding operation continues.

            Up next: Array basics.

            CompleteFrontend Clear concepts. Working examples.