Briefly
Imagine a situation: we have a form with a field where the user enters their age in years.
By default, any input in fields is a string. If we want to work with this value as a number, we need to cast it to a number.
Casting (or type conversion) is the process of converting a value from one type to another.
In JavaScript, types can be converted explicitly and implicitly.
When we call a function to get a specific type — this is explicit conversion:
const x = '4'Number(x)const y = 4String(y)
const x = '4' Number(x) const y = 4 String(y)
Comparison can be strict or loose. In strict comparison (=
), the interpreter takes into account the types of the compared values.
When we compare values loosely using =
, JavaScript converts types on its own:
console.log(5 == '5')// trueconsole.log(5 === '5')// false
console.log(5 == '5') // true console.log(5 === '5') // false
To understand why this is so, we first need to figure out what types exist in JS.
First, we will draw a line between primitive types, objects, and others.
Primitive Types
In JavaScript, primitive types are the following:
// 1. Undefinedtypeof undefined === 'undefined'// 2. Booleantypeof true === 'boolean'typeof false === 'boolean'// 3. Numbertypeof 42 === 'number'typeof 4.2 === 'number'typeof -42 === 'number'typeof Infinity === 'number'typeof -Infinity === 'number'// 4. Stringtypeof '' === 'string'typeof 'string' === 'string'typeof 'number' === 'string'typeof 'boolean' === 'string'// 5. Symbol, ES6typeof Symbol() === 'symbol'// 6. BigInt, ES6typeof 9007199254740991n === 'bigint'typeof BigInt(9007199254740991) === 'bigint'// 7. Nulltypeof null === 'object'// We'll discuss why it's "object" a bit later.
// 1. Undefined typeof undefined === 'undefined' // 2. Boolean typeof true === 'boolean' typeof false === 'boolean' // 3. Number typeof 42 === 'number' typeof 4.2 === 'number' typeof -42 === 'number' typeof Infinity === 'number' typeof -Infinity === 'number' // 4. String typeof '' === 'string' typeof 'string' === 'string' typeof 'number' === 'string' typeof 'boolean' === 'string' // 5. Symbol, ES6 typeof Symbol() === 'symbol' // 6. BigInt, ES6 typeof 9007199254740991n === 'bigint' typeof BigInt(9007199254740991) === 'bigint' // 7. Null typeof null === 'object' // We'll discuss why it's "object" a bit later.
Primitive types are such types whose values can only be overwritten but not changed.
For example, if we create a variable with a value of 42
, it cannot be changed. We can only completely overwrite it:
let theAnswerToUltimateQuestion = 42theAnswerToUltimateQuestion = 43// The new value has completely overwritten the old one;// the old one is collected by the garbage collector and forgotten.let theAnswers = [42, 43, 44]theAnswers[0] = 142// Now the variable value is [142, 43, 44];// we did not overwrite it completely, but merely changed a part.
let theAnswerToUltimateQuestion = 42 theAnswerToUltimateQuestion = 43 // The new value has completely overwritten the old one; // the old one is collected by the garbage collector and forgotten. let theAnswers = [42, 43, 44] theAnswers[0] = 142 // Now the variable value is [142, 43, 44]; // we did not overwrite it completely, but merely changed a part.
This mechanism is related to how variable values are stored in memory. We won't go too deep into this topic, but broadly speaking, primitive types "refer to the same value in memory," while non-primitive types refer to different ones. We explore this issue further in the article “Reference vs Value Storage”.
Because of this, for instance, primitives can be compared by value:
const a = 5const b = 5console.log(a == b)// true
const a = 5 const b = 5 console.log(a == b) // true
But non-primitives cannot be:
const a = [1, 2, 3]const b = [1, 2, 3]console.log(a == b)// false
const a = [1, 2, 3] const b = [1, 2, 3] console.log(a == b) // false
Even though the arrays contain the same numbers, they are not considered "equal" when compared. When JavaScript compares a
and b
, it is, roughly speaking, "comparing the memory locations these variables refer to." For non-primitives, these locations are different, which is why they are considered unequal.
Objects
Objects in JavaScript are used to store collections of values.
Arrays (Array) in JS are also objects.
As we mentioned, non-primitives are compared by reference, not by value. Objects and arrays are indeed non-primitives.
Objects in JavaScript have their own type — object
.
const keyValueCollection = { key: 'value' }typeof keyValueCollection === 'object'const listCollection = [1, 2, 3]typeof listCollection === 'object'
const keyValueCollection = { key: 'value' } typeof keyValueCollection === 'object' const listCollection = [1, 2, 3] typeof listCollection === 'object'
For null
, the typeof
operator returns 'object'
, even though it is also a primitive:
console.log(typeof null === 'object')// true
console.log(typeof null === 'object') // true
Functions
Functions in JavaScript also have a type — object
, although typeof
returns 'function'
:
function simpleFunction() {}console.log(typeof simpleFunction === 'function')// trueconst assignedFunction = function () {}console.log(typeof assignedFunction === 'function')// trueconst arrowFunction = () => {}console.log(typeof arrowFunction === 'function')// trueconsole.log(typeof function () {} === 'function')// true
function simpleFunction() {} console.log(typeof simpleFunction === 'function') // true const assignedFunction = function () {} console.log(typeof assignedFunction === 'function') // true const arrowFunction = () => {} console.log(typeof arrowFunction === 'function') // true console.log(typeof function () {} === 'function') // true
We have described the differences between different types of functions in the article “Functions”.
typeof
The typeof
operator returns not directly the "type," but a string. For all primitives except null
, this string will be the name of that primitive.
For objects, it first checks if it can be "called". Functions are just such objects, so the operator returns function
.
Although typeof
does not always return what we might expect, it is convenient to use in certain cases in code, for example, for determining functions.
Type Conversion
Now that we have unraveled the types, let's see how we can convert values of one type into values of another.
In JavaScript, there are only 3 types of conversion: to string, to number, or to boolean.
To convert a value to these types, we can use the functions of the same name:
String(42) // Converts to string.Number('42') // Converts to number.Boolean(42) // Converts to boolean.
String(42) // Converts to string. Number('42') // Converts to number. Boolean(42) // Converts to boolean.
Casting to string, number, and boolean can be performed on any values:
// To string:String(123) // '123'String(-12.3) // '-12.3'String(null) // 'null'String(undefined) // 'undefined'String(true) // 'true'String(false) // 'false'String(function () {}) // 'function () {}'String({}) // '[object Object]'String({ key: 42 }) // '[object Object]'String([]) // ''String([1, 2]) // '1,2'
// To string: String(123) // '123' String(-12.3) // '-12.3' String(null) // 'null' String(undefined) // 'undefined' String(true) // 'true' String(false) // 'false' String(function () {}) // 'function () {}' String({}) // '[object Object]' String({ key: 42 }) // '[object Object]' String([]) // '' String([1, 2]) // '1,2'
You can also attempt to convert any values to a number. If JavaScript cannot convert a certain value to a number, we get NaN
— a special value representing Not-a-Number.
// To number:Number('123') // 123Number('123.4') // 123.4Number('123,4') // NaNNumber('') // 0Number(null) // 0Number(undefined) // NaNNumber(true) // 1Number(false) // 0Number(function () {}) // NaNNumber({}) // NaNNumber([]) // 0Number([1]) // 1Number([1, 2]) // NaN// Note that Number of an empty array is 0,// of an array with one number — that number// and of an array with multiple numbers — NaN.// We will understand why this is later.
// To number: Number('123') // 123 Number('123.4') // 123.4 Number('123,4') // NaN Number('') // 0 Number(null) // 0 Number(undefined) // NaN Number(true) // 1 Number(false) // 0 Number(function () {}) // NaN Number({}) // NaN Number([]) // 0 Number([1]) // 1 Number([1, 2]) // NaN // Note that Number of an empty array is 0, // of an array with one number — that number // and of an array with multiple numbers — NaN. // We will understand why this is later.
You can also convert any values to boolean:
Boolean('') // falseBoolean('string') // trueBoolean('false') // trueBoolean(0) // falseBoolean(42) // trueBoolean(-42) // trueBoolean(NaN) // falseBoolean(null) // falseBoolean(undefined) // falseBoolean(function () {}) // trueBoolean({}) // trueBoolean({ key: 42 }) // trueBoolean([]) // trueBoolean([1, 2]) // true// Broadly speaking, everything except for an empty string, zero,// NaN, null, and undefined — is true.
Boolean('') // false Boolean('string') // true Boolean('false') // true Boolean(0) // false Boolean(42) // true Boolean(-42) // true Boolean(NaN) // false Boolean(null) // false Boolean(undefined) // false Boolean(function () {}) // true Boolean({}) // true Boolean({ key: 42 }) // true Boolean([]) // true Boolean([1, 2]) // true // Broadly speaking, everything except for an empty string, zero, // NaN, null, and undefined — is true.
Implicit Type Conversion
In the previous section, we converted types "manually," using functions. But JavaScript can perform such conversions for us automatically. (This leads to many quirks for which it is not very fond.)
This type of typing, where the type of a value is determined at assignment time and can change along the program, is called dynamic.
Implicit conversion occurs when we force JavaScript to work with values of different types. For instance, if we want to "add" a number and a string:
5 + '3' === '53'5 - '3' === 25 + '-3' === '5-3'5 - +3 === 25 + -3 === 2// Because of this, there arose this joke:Array(16).join('wat' - 1) + ' Batman!'// 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
5 + '3' === '53' 5 - '3' === 2 5 + '-3' === '5-3' 5 - +3 === 2 5 + -3 === 2 // Because of this, there arose this joke: Array(16).join('wat' - 1) + ' Batman!' // 'NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!'
The reason lies in how JavaScript tries to "match" these two types together to work with them.
Let's first look at primitives.
- The interpreter will convert primitive values to boolean if we use
&&
or||
. - To string, if we use
+
, when one of the operands is a string. - To number if:
- we use comparison operators
<
,<
,= >
,>
;= - use arithmetic operations
-
,+
(except for point 2),/
,*
. - we use the unary plus:
+'2'
;= = = 2 - we use the loose equality operator
=
.=
- we use comparison operators
But it doesn’t stop at primitives; JavaScript also implicitly converts non-primitive values.
The interpreter converts them to boolean if we use &&
or ||
. Objects are always true
.
When it comes to numbers and strings, things are a bit more interesting. To determine whether to convert a value to a string or a number, JavaScript checks which of the two methods (value
and to
) is defined in the current object.
- If the object is not a
Date
, thevalue
method is called first (not delving too deeply into the details of the specification).Of ( ) - If the returned value is a primitive, it is returned.
- If not, the other method is called (if
value
did not return a primitive, thenOf ( ) to
is called, and vice versa).String ( ) - If a primitive is returned afterward, it is returned.
- If even after this a primitive does not return, there will be an error
Uncaught TypeError
.: Cannot convert object to primitive value
Examples
// 1. Simple objectconst obj1 = {}obj1.valueOf() // {}obj1.toString() // '[object Object]'// To "add" a number with an object,// the first call will be obj1.valueOf().// It will return the object (non-primitive),// after which obj1.toString() will be called.1 + obj1// 1 + '[object Object]'// '1' + '[object Object]'// '1[object Object]'// 2. Object with a specified .valueOf()const obj2 = {}obj2.valueOf = () => 'obj2'obj2.valueOf() // 'obj2'obj2.toString() // '[object Object]'// Now when we declared the .valueOf() method,// it will return a string when called.// Since a string is a primitive,// it will be used in "addition."1 + obj2// 1 + 'obj2'// '1' + 'obj2'// '1obj2'// 2.1. If we were to return a numberconst obj2 = {}obj2.valueOf = () => 42obj2.valueOf() // 42obj2.toString() // '[object Object]'1 + obj2// 1 + 42// 43// 3. Datesconst date = new Date()date.valueOf() // 1467864738527date.toString() // 'Sun Sep 15 2019...'// For dates, the order of methods is reversed:// which means .toString() will be called first,// and only after that — .valueOf().1 + date// 1 + 'Sun Sep 15 2019...'// '1' + 'Sun Sep 15 2019...'// '1Sun Sep 15 2019...'
// 1. Simple object const obj1 = {} obj1.valueOf() // {} obj1.toString() // '[object Object]' // To "add" a number with an object, // the first call will be obj1.valueOf(). // It will return the object (non-primitive), // after which obj1.toString() will be called. 1 + obj1 // 1 + '[object Object]' // '1' + '[object Object]' // '1[object Object]' // 2. Object with a specified .valueOf() const obj2 = {} obj2.valueOf = () => 'obj2' obj2.valueOf() // 'obj2' obj2.toString() // '[object Object]' // Now when we declared the .valueOf() method, // it will return a string when called. // Since a string is a primitive, // it will be used in "addition." 1 + obj2 // 1 + 'obj2' // '1' + 'obj2' // '1obj2' // 2.1. If we were to return a number const obj2 = {} obj2.valueOf = () => 42 obj2.valueOf() // 42 obj2.toString() // '[object Object]' 1 + obj2 // 1 + 42 // 43 // 3. Dates const date = new Date() date.valueOf() // 1467864738527 date.toString() // 'Sun Sep 15 2019...' // For dates, the order of methods is reversed: // which means .toString() will be called first, // and only after that — .valueOf(). 1 + date // 1 + 'Sun Sep 15 2019...' // '1' + 'Sun Sep 15 2019...' // '1Sun Sep 15 2019...'
Strict and Loose Equality
Implicit conversion is also used when we compare values using loose equality =
.
In contrast to strict equality (=
), in it the interpreter tries to convert types to one another for comparison.
The complete algorithm is complex. For convenience, it has been condensed into a large matrix, showing "what is equal to what" under strict and loose equality.
Here is the loose equality table (in green are marked values that are "equal"):

And here is the one for strict:

It is considered good practice to use only strict comparison to avoid implicit type conversion when comparing.
In practice
Advice 1
Always use strict equality when comparing values.
🛠 For convenience, the existence check of an object can be performed with if
because objects are always coerced to true
.
const exists = {}if (exists) { /* this branch will be executed */}const doesntExist = undefinedif (doesntExist) { /* this branch will not be executed */}
const exists = {} if (exists) { /* this branch will be executed */ } const doesntExist = undefined if (doesntExist) { /* this branch will not be executed */ }
🛠 If you want to describe a complex structure that can "behave" like a number or string, you can describe the methods .value
or .to
.
const ticketPrice = { amount: 20, currency: 'USD', valueOf: () => 20, toString: () => '$20',}1 + ticketPrice // 1 + 20 -> 21console.log(ticketPrice)// $20
const ticketPrice = { amount: 20, currency: 'USD', valueOf: () => 20, toString: () => '$20', } 1 + ticketPrice // 1 + 20 -> 21 console.log(ticketPrice) // $20