Types are a fundamental JavaScript concept.
Here are some questions to warm you up!
console.log(Boolean(Boolean(false)))
You might encounter questions like this in a typical JavaScript Interview.
Let's understand the concepts at play here, so that you can answers these questions with confidence in your next JS interview.
Primitive Types
You might have heard that Everything in JS is an object, Well this is not true.
The reason people say it is because, Almost everything in JS can behave like an object (thanks to boxing
), but that does not make them object themselves.
Boxing
Even though string is a primitive type, how can we access methods and attributes on it. Well this is due to boxing, wherein JS says, well str
is not an object but you are trying to use it as an object to let me convert (coerce) it into an object.
We have 7 primitive types in JS:
-
string
: e.g.,'hello', 'world'
-
number
: e.g.,7, 3.14, NaN
-
bigint
: For very large numbers e.g.,123n
-
boolean
:true
orfalse
-
undefined
: a variable that has been declared but not assigned a value -
symbol
: unique and immutable value, often used as object keys -
object
: e.g.,{}
,[]
,() => {}
Arrays and Functions are also of the
object
type.
Honorable mention:
Now null
should have been its own type, but it has object
as its type.
This is a long standing bug in JS and it likely will stay as is.
Since fixing it now would break a lot of applications
Fun fact: The first version of JS was written bty Brendon Eich in just 10 days.
typeof
To check the type of a variable use the typeof
keyword.
Unlike languages like C++ (statically typed), in the dynamically typed languages like JS it's not the variables that have types, but the values do.
Therefore when you console typeof <variable-name>
, it tells the type of the value in the variable.
typeof
always returns a string, from a set enum
of values like (undefined, string, number, ...). So always use string when checking for types.
OK, so the cases till now were straight forward, let's get to some weird part of typeof
So we know that objects, functions and array are all of type object.
Then why do typeof
returns function
as a type for a function?
All functions in JavaScript are objects.
But the typeof
operator was designed to return 'function'
for functions, even though they are technically of type object, to make it easier for developers to identify functions.
You might then argue that why then typeof
returns object for an array and not array.
Arrays are objects, and JavaScript’s typeof
operator only returns 'object'
for all non-function objects, including arrays.
typeof
was designed in the early days of JavaScript, and it's a bit inconsistent.
The 'function'
type was added later as a special case but typeof
never got similar special handling for arrays.
Why not change typeof [] == "array"
now ?
Again doing it now would break a lot of things, that expect the type of an array to return object.
So how am I supposed to know if a variable is an array or an object, if typeof
returns object in both the cases.
We use Array.isArray
in that case.
Special Values
NaN
NaN
is a special value of type number, that signifies invalid number.
Let's consider this example:
NaN
is the only value in JS that does not hold the Identity property, and hence it is not equal to itself.
To check if a value would convert into NaN
on number conversion use the isNaN
function.
Notice that even the string values like 'test'
and 'NaN'
that convert to NaN
on number conversion are returning true
when checking with isNaN
function.
If you want to explicitly check for just the NaN
keyword use Number.isNaN
function
Negative Zero
Just like we have negative numbers, JS also has negative 0. Think of it like 0 is the magnitude and - represents the direction. So negative zero can be used to represent an object that has stopped moving backwards.
Let's see how it behaves:
As you saw on using toString()
on x we get 0. Hence -0 === 0
because of string conversion that happened.
To check if a number is -0 use object.is()
Fundamental types
In addition to the primitive types we also have fundamental objects, (aka : Built-in Objects or Native functions)
The fundamental objects, are not really types but they provide a class based form for the primitive types. We use the new
keyword to create a constructor of these types. More Java like behavior!
Strings, Number and Boolean should be used as functions and not constructors. Since when used as a function string, number and boolean coerce any value to that respective primitive type. We will discuss that in the Boolean section
Coercion
Coercion is just a fancy name for conversion in JS.
Let's look at how string, number and boolean handle type conversions.
If we are using non-primitive types in an expression. Then JS will recursively use the primitive functions (String
, Number
, Boolean
) to convert the types until we get a primitive value or an error.
Now the order in which these functions get called depends on the type of operation. We will talk about that a bit later, first let's see what happens to different variables on using these functions.
String
On Array:
Notice
Also here one comma is removed
On Object:
For any object String()
would return '[object Object]'
Number
Notice
On Array & Object:
Calling Number()
on arrays and objects, returns the same object, so String()
is called next. And then the process continues till we get a primitive type or an error.
Let's understand this with an interactive example:
Number([""])
Let's start with an empty array, and see how it converts to a number.
Similarly we have
Let's see another example:
Number([null])
Let's start with an array with a null value, and see how it converts to a number.
Similarly we have
One more?
Number({})
This time let's try to convert an object to a number.
Boolean
For boolean it's a simple table look up, no conversion.
-
false
-
0
/-0
-
NaN
-
""
(empty string) -
null
-
undefined
Apart from these six values everything else is truthy!
Consider the following example:
Since, new Boolean is a object wrapper for the primitive boolean type, hence it returns an object. And object is not in the 6 falsy values, we get true.
That's why you should not use the new keyword for String(). Number() & Boolean()
.
Order of operation
Now that we know how things convert using String
, Number
and Boolean
.
Let's see the order:
Addition
- Both operands are first coerced to primitives (via
ToPrimitive
). - If either resulting primitive is a string, JavaScript performs string concatenation—it converts the other operand to a string (via
ToString
) and joins them. - Otherwise, it converts both operands to numbers (via
ToNumber
) and performs numeric addition.
Let's run through some examples:
"1" + 123
Since both the operands are of primitive types, ToPrimitive()
is not called.
1 + true
Since both the operands are of primitive types, ToPrimitive()
is not called.
Subtraction, Multiplication, Division, Remainder, Exponentiation
- These always convert both operands to numbers (using ToNumber) and then perform the numeric operation.
- If either conversion yields NaN, the result is NaN
Relational Operators
JavaScript’s relational operators (<, >, <=, >=
) use the Abstract Relational Comparison algorithm, which basically works like this:
-
Evaluate operands left-to-right
- They’re not all done “simultaneously” –
a < b < c
is interpreted as(a < b) < c
.
- They’re not all done “simultaneously” –
-
ToPrimitive → ToNumber or string comparison
- Both operands are first coerced to primitives (via
ToPrimitive
). - If after that both are strings, they’re compared lexicographically (dictionary order, character by character, using Unicode code points).
- Otherwise, both are converted to numbers (ToNumber) and compared numerically.
- Both operands are first coerced to primitives (via
All this might sound a bit complicated, but it’s actually quite straightforward once you see it in action.
Consider the following examples:
Let's break it down step by step.
1 < 2 < 3
Let's start with the first example 1 < 2 < 3
Now to the interesting example:
3 > 2 > 1
Consider 3 > 2 > 1
Let's do one more example:
"200" < "30"
Since both the operands are strings, we compare them lexicographically (dictionary order, character by character, using Unicode code points).
==
Vs ===
The first line for the ==
in the spec says that if the types of the 2 operand are same use the ===
Otherwise using ==
would try to convert both the values to same type and return the result.
So the real difference between ==
and ===
is that ==
allows coercion to happen but ===
does not. The == prefers Numeric comparisons, so if either of x or y is a number it tries to convert the other one to Number as well
Also in case of ==, if there is a non-primitive the toPrimitive()
is called and once it becomes a primitive the comparison is made
Let's now look at the example from the quiz:
We saw that [] == ![]
is true
.
Let's see why?
[] == ![]
Let's start with the expression [] == ![]
Now this is not how you should do Boolean comparisons.
The correct expression should be [] != []
Now even this is true
, but the reason is different.
Here we are comparing two different arrays, and since they are not the same object, they are not equal.
Note:
Objects are compared by reference, not by value. So two different arrays are not equal, even if they have the same values.
Exercise
Now with all that knowledge, see if you can tell the output of the following code:
Now the output may not have made sense.
To understand it clearly, you need to understand the difference between Boolean Context & Equality Comparison.
Let's see each of the if statements one by one:
-
Here
a
is an array, and arrays are truthy values.
So this condition is true, and we print "yes 1".
This was the boolean context. No coercion happened here.
-
Here we are comparing an array with a boolean. So we convert the array to a string, which is
""
, and then convert that to a number, which is0
.Then we compare
0
withtrue
, which is1
.So this condition is false, and we don't print anything.
This is called Equality Comparison.
-
Here we are comparing the negation of an array with the negation of
true
. The negation of an array isfalse
, and the negation oftrue
is alsofalse
.So this condition is true, and we print "yes 3". This is also a Boolean Context.
-
Here we are comparing an array with
false
.So we convert the array to a string, which is""
, and then convert that to a number, which is0
.Then we compare
0
withfalse
, which is also0
.
So this condition is true, and we print "yes 4". This is also an Equality Comparison.
Moral of the story:
We just perform the boolean table lookup in the boolean context, but in the equality comparison we convert the values to a common type and then compare them.
Therefore is recommended to make sure that the !
(not) sign is with the equal to symbol, and not with the value, otherwise it would first do a Boolean table lookup and produce weird results.
The Special Case of null
JSON.stringify()
, converts undefined to null.
null
is only equal to undefined
or itself using the ==
operator.
Note:
When null is used with ==
it is treated as +0
.
When null is used with >, <, <=, >=
then it is treated at 0
.
Also,
If we add + in front of null
the coercion happens, and +null
becomes 0. Hence:
Similarly -null
becomes -0
Wrapping Up
Understanding JavaScript's type system is crucial for writing robust code and succeeding in technical interviews. Let's recap the key insights from the blog.
The Core Principles
Types belong to values, not variables. Unlike statically typed languages, JavaScript's dynamic nature means a single variable can hold different types throughout its lifetime. This fundamental concept underlies everything else we've explored.
Coercion is predictable, not magical. While JavaScript's type conversions might seem mysterious at first, they follow consistent rules. The String(), Number(), and Boolean()
functions are your guide to understanding how any value will be converted.
Critical Distinctions to Remember
The difference between boolean context and equality comparison is crucial to understand. A value might be truthy in an if statement but still equal false when compared directly. This distinction trips up many developers and is a favorite topic in interviews.
Similarly, ==
vs ===
isn't just about "loose" vs "strict" - it's about whether you want coercion to happen. Both have their place, but understanding when coercion occurs helps you choose the right tool.
Practical Takeaways
- Use
Array.isArray()
instead of relying ontypeof
for arrays - Remember the six falsy values - everything else is truthy
- Be cautious with implicit coercion in operations, especially with mixed types
- When in doubt, be explicit with your type conversions
Final Thoughts
These concepts might seem like JavaScript quirks, but they reflect the language's flexibility and its origins as a quickly-developed scripting language that has evolved into a powerful programming platform.
Understanding these behaviors helps you write more predictable code and debug issues more effectively.
With this foundation, you're now equipped to handle even the trickiest type-related questions in interviews and write more confident JavaScript in your projects. The key is practice - try to predict the outcome of expressions before running them, and you'll internalize these patterns quickly.
Where to go from here?
I highly recommend the You Don't Know JS series by Kyle Simpson. It dives deep into JavaScript's mechanics, including types and coercion, and is a fantastic resource for mastering the language.