[object Object] - An small guide about how object coercion works in JS
![[object Object] - An small guide about how object coercion works in JS](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F6241235b8cf84ebed8133d31%2F0079ee7b-da0b-4701-b839-ffd2e6beadc6.jpg&w=3840&q=75)
Have you ever tried casting an object instance to a string in JS? It could have happened by mistake or be intentional, but I can almost guarantee that the result of the operation was a [object Object] printed on your screen.
function Cat (n) {
this.name = n
}
const theCat = new Cat('Josephine')
'a' + theCat // a[object Object]
`${theCat}` // [object Object]
Surely this is the default behavior for all the instances of objects in JS, right? But wait, if that is true, then why casting a Date instance to a string actually returns something readable?
`${new Date()}` // 'Mon Apr 06 2026 20:00:11 GMT-0600 (Central Standard Time)'
Is the toString() method or something else? To be sincere, toString is part of the answer, but there is a bigger picture you need to understand to get the complete answer.
Type Coercion
I won't go in depth about Type Coercion in general here. It is enough to know that JS will try to cast operands to a primitive value based on the operation being performed. For example:
// Math Operations
2 * '5' // 4
'10' / 5 // 2
7 - '2' // 5
4 + '4' // 44 ... wait, what?
For additions, if any of the operands is a string, a concatenation will be done instead of the addition.
Casting to primitives is done in the same way for object instances. So, if it is done the same way, why the [object Object] ? To answer that, you need to understand how the Primitive coercion works.
Primitive coercion
JS follows a "protocol" when casting objects to a primitive type. The type of operation you are performing will determine the order in which the methods to get the primitive value will be called.
For numeric coercion, the chain call will be:
toPrimitive > valueOf() > toString()
For string coercion, the order will be:
toPrimitive > toString() > valueOf()
The toPrimitive method must return a primitive. If it returns an Object, a TypeError will be thrown. In the case that the toPrimitive is not set, the next operation on the chain will be called. valueOf() or toString() should return a primitive value, too. In case it returns an object, it will proceed with the next operation. If neither of the operations is present or returns a primitive, then a TypeError will be thrown.
toPrimitive
The toPrimitive attribute is a property that holds a reference to a function that will be called when a coercion to a primitive needs to be done. This function will receive a hint as parameter that will tell what type of operation is being performed, and it can be used to decide what primitive value to return.
The hint can be any of the following values:
number: when the object is used in a numeric operation (*,/,-, etc)string: when the object is used in a string operation, like using it with string interpolation (e.g.`${variable}`)default: when the object is used in an operation with mixed types, where JS can't determine what type to use. Normally these are operations with the+sign or in loose equality comparisons==.
const myCat = { name: 'Josephine', type: 'Tabby', id: 112 }
myCat[Symbol.toPrimitive] = function (hint) {
if (hint === 'string') return this.name;
if (hint === 'number') return this.id;
return this.id; // default case
}
+myCat // Coercing to number: 112
`${myCat}` // Coercing to string: Josephine
1 + myCat // Coercing to default - returning id: 113 (Just for demonstration purposes :D )
valueOf()
This method is available in all object instances, and is called by JS when coercing an object to a primitive. Unlike the toString() method, valueOf() can return any other primitive besides a string. By default, this method will return the object itself.
function Cat (n, id) {
this.name = n;
this.id = id;
}
const theCat = new Cat('Josephine', 112)
Cat.prototype.valueOf = function () { return this.id }
+theCat // Coercing to number: 112
toString()
As the name indicates, the toString returns a string that represents the object. When called on objects that have not customized it, it returns the default value inherited from Object global type.
function Cat (n, id) {
this.name = n;
this.id = id;
}
const theCat = new Cat('Josephine', 112)
`${theCat}` // [object Object]
Aja! Here is the culprit. This is why [object Object] is printed.
If needed, toString can be overwritten to return a meaningful string:
function Cat (n, id) {
this.name = n;
this.id = id;
}
const theCat = new Cat('Josephine', 112)
Cat.prototype.toString = function () { return this.name }
`${theCat}` // Josephine
So... about the Date
Now that you understand better how the coercion to primitive works, it is easier to give a proper answer about why the Date was returning a readable string.
The short answer: toPrimitive. The Date prototype establishes that depending on the hint given, a string or a timestamp will be returned. In the example from above, the date was used in a string operation, so a string was returned.
Conclusion
Understanding how the Primitive Coercion works in JS opens new possibilities on how you can enrich your codebase to apply maintainable and scalable patterns. Taking control of how the primitive values are generated can allow you to build better logging systems, or simplify the interfaces of your objects so you can perform operations like Math between object instances.
Hope this small article helped you understand a part of how Coercion works in JS. The topic goes further with subjects like numeric coercion, string coercion, boolean coercion, etc. Keep an eye on those as well ;).
This was inspired by the course Javascript: The Hard Parts, v3 by Will Sentance on Frontend Masters.
