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.
- 01Predict the hintKnow when JavaScript asks an object for
string,number, ordefault. - 02Trace the fallback orderExplain when
Symbol.toPrimitive,valueOf, andtoStringrun. - 03Design 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.
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 +.
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
stringhint - In real life: A field asks for a number
- In JavaScript: The
numberhint - In real life: A field asks for a useful answer
- In JavaScript: The
defaulthint - 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 RULEJavaScript does not randomly pick between methods. It follows this order every time an object needs to become a primitive:
- If
obj[Symbol.toPrimitive]exists, call it with one argument: the hint"string","number", or"default". - If that method returns a primitive, use it. If it returns an object, throw
TypeError. - If there is no symbol method and the hint is
"string", trytoString(), thenvalueOf(). Use the first primitive result. - If there is no symbol method and the hint is
"number"or"default", tryvalueOf(), thentoString(). Use the first primitive result. - If none of those calls produces a primitive, throw
TypeError.
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 THROUGHSymbol.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.
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
hintargument - 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 custom Symbol.toPrimitive method lets one object answer differently for text and math.
script
cents: 1299, [Symbol.toPrimitive](hint) { if (hint === "string") return "₹12.99"; return this.cents / 100; }}; String(price);+price;price + 1;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
INTERACTIVEMost 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.
| Hint | First method | Second method | Everyday example |
|---|---|---|---|
string | toString() | valueOf() | String(obj) and template literals want text. |
number | valueOf() | toString() | Unary +obj, obj * 2, and relational comparison want numbers. |
default | valueOf() | 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.
Pick an object shape, then step through the real fallback order for string, number, and default conversions.
script
const sample = { valueOf() { calls.push("valueOf"); return 42; }, toString() { calls.push("toString"); return "forty-two"; }}; String(sample);+sample;sample + "!";The string, number & default hints
INTERACTIVEThe 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.
| Operation | Hint | Why |
|---|---|---|
String(obj) | string | Explicit string conversion. |
Template ${obj} | string | Interpolation creates text. |
[obj].join() | string | Joining array elements converts each element to text. |
+obj | number | Unary plus only means numeric conversion. |
obj * 2 | number | Multiplication only does numeric math. |
obj > 0 | number | Relational comparison converts objects before comparing. |
obj + 1 | default | Binary plus may add or concatenate, so it asks neutrally first. |
obj == 1 | default | Loose 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.
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();The selected operation is String(obj). The object logged the string hint and produced "object-card".
Symbol.toPrimitive; the playground only lets you choose from the visible operations.String(obj)`${obj}`[obj].join()+objobj * 2obj > 0obj + 1obj == 1
Sort each operation by the hint it sends to an object.
Arrays, objects & dates explain the classic puzzles
REAL OUTPUTSArrays are objects, but their toString() method behaves like join(","). That one fact explains many early JavaScript surprises.
String({});String([]);[1, 2] + "";[5] * 2;[1, 2] + [3];[] + {};({} + []);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] * 2becomes"5" * 2, then numeric multiplication gives10.[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 parseas a block, so the remaining+[]is0. 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:
- Readable logging and templates. A useful
toString()keeps debugging output from showing"[object Object]". - Value objects. Money, measurements, temperatures, and IDs sometimes deserve a small, well-tested primitive conversion.
- Debugging coercion. When
+,==, or comparison surprises you, you can ask “which hint did this send?” instead of guessing.
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
defaulthint, 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
numberanddefaultordinary fallback. Thestringhint triestoString()first. - “Plain objects cannot become strings.” They can: inherited
toString()returns"[object Object]". - “Dates behave like ordinary objects.” Date treats the
defaulthint as string-like, which is whydate + 1concatenates. - “Returning an object from Symbol.toPrimitive falls back.” It throws
TypeError. The custom hook must return a primitive.
Practice exercises
5 EXERCISESWhat hint does JavaScript send to an object in obj + 1?
obj + 1 sends the default hint. After both sides are primitive, + decides whether to add or concatenate.
Symbol.toPrimitive for a priceAdd a conversion method so String(price) is "₹25.00" and price + 5 prints 30. Type the second printed value.
const price = {
cents: 2500,
// add Symbol.toPrimitive here
};
console.log(String(price));
console.log(price + 5);const price = {
cents: 2500,
[Symbol.toPrimitive](hint) {
if (hint === "string") return "₹25.00";
return this.cents / 100;
}
};
console.log(String(price));
console.log(price + 5);The string hint returns a formatted label. The default hint in price + 5 returns 25, so the printed result is 30.
Predict both lines. Put the first output and second output in the answer box.
console.log([5] * 2);
console.log([1, 2] + [3]);[5] * 2 prints 10. [1, 2] + [3] becomes "1,2" + "3", so it prints 1,23.
[object Object] in a templateThe starter code prints Hello, [object Object]!. Add a method so the template prints the user’s name.
const user = { name: "Ada" };
console.log(`Hello, ${user}!`);const user = {
name: "Ada",
toString() {
return this.name;
}
};
console.log(`Hello, ${user}!`);Adding toString() gives template literal interpolation a useful primitive string, so it prints Hello, Ada!.
date + 1In one phrase, explain whether date + 1 is string concatenation or numeric addition.
date + 1 uses the default hint. Date handles default like string, so the date becomes text and 1 is concatenated. +date would ask for number and produce a timestamp.
Check your understanding
7 QUESTIONSQuestion 1 of 7What happens first when an object has
Symbol.toPrimitive?Choose an answer to see the explanation.
Question 2 of 7What does this object plus expression print?
Read the code, then predictconst obj = { valueOf() { return 4; } }; console.log(obj + 1);Choose an answer to see the explanation.
Question 3 of 7Which operation sends the
stringhint?Choose an answer to see the explanation.
Question 4 of 7What does this array multiplication print?
Read the code, then predictconsole.log([5] * 2);Choose an answer to see the explanation.
Question 5 of 7What does this array plus expression print?
Read the code, then predictconsole.log([1, 2] + [3]);Choose an answer to see the explanation.
Question 6 of 7What if
Symbol.toPrimitivereturns{}?Choose an answer to see the explanation.
Question 7 of 7Why is
date + 1usually text, while+dateis a timestamp?Choose an answer to see the explanation.
Key takeaways
Symbol.toPrimitiveis checked first and receives the hint.- If the custom hook returns an object, JavaScript throws
TypeError. - Without the symbol hook,
stringtriestoString()beforevalueOf(). - Without the symbol hook,
numberanddefaulttryvalueOf()beforetoString(). - Binary
+and loose equality use thedefaulthint; unary plus, multiplication, and relational comparison usenumber; templates andString()usestring. - 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.