Sitemap

The Difference Between == and === in JavaScript

9 min readMay 7, 2025

--

Image Source

JavaScript has two equality operators that confuse a lot of people, especially early on. They both check if two values are equal, but they don’t always give you the same answer. One is the double equals (==), often called loose equality. The other is triple equals (===), or strict equality. You’ll hear advice to “always use ===” and to “never trust ==,” but that doesn’t really explain why things work the way they do.

I publish free articles like this daily, if you want to support my work and get access to exclusive content and weekly recaps, consider subscribing to my Substack.

How Loose and Strict Equality Actually Work

This is where all the strange stuff around equality in JavaScript starts to make more sense. The two operators, == and ===, do the same kind of job on the surface. But the way they do that is totally different. That difference can lead to results that feel wrong unless you know what’s happening underneath.

What === Does Internally

Triple equals is the one that doesn’t try to guess. When you use ===, JavaScript checks if both the value and the type are the same. If either one doesn’t match, it gives you false without doing any conversions.

'5' === 5        // false, different types
false === 0 // false, one is boolean, the other is number
true === true // true, same value and type
null === null // true, both are the exact same null

It’s the more predictable one because it doesn’t touch the values. It just checks what’s there.

Objects and arrays behave a little differently, though. You might think that two arrays with the same content are equal, but that’s not how JavaScript sees it.

[] === []        // false
{} === {} // false

Both of those comparisons return false because they’re comparing two different objects in memory. Even if the structure is the same, they’re not the same instance. But if you assign one to a variable and compare it to itself, it works.

let a = [];
let b = a;
a === b // true

Here, both a and b point to the same array, so the comparison returns true. This also applies to functions and objects in general. If two things weren’t created from the same spot in memory, strict equality doesn’t consider them equal.

function f() {}
f === f // true

function g() {}
f === g // false

In this, even though both f and g are empty functions, they’re different ones. So the comparison returns false.

What == Does Behind the Scenes

Double equals looks similar, but in the background it works differently. If the two values are different types, JavaScript will try to convert one or both before doing the comparison. This is called type coercion. It doesn’t warn you when it does this, it just does it.

'5' == 5          // true
false == 0 // true
null == undefined // true

You’re getting true in all of these because JavaScript is turning one value into the type of the other. In '5' == 5, the string '5' gets converted into the number 5, so now it’s 5 == 5, which is true. With false == 0, the boolean becomes a number. false becomes 0, so again, 0 == 0.

It keeps going from there. Here’s another strange one:

[] == ''          // true
[] == 0 // true
'' == 0 // true

These all return true, and it’s not obvious why at first. Let’s walk through [] == 0. JavaScript turns the array into a string first. An empty array becomes an empty string. Then it compares '' == 0. Now it converts the empty string into the number 0. So it’s actually comparing 0 == 0, which is true.

That same logic explains why this is true:

false == []       // true

What’s happening is that false gets turned into 0, and the empty array becomes an empty string, which then also turns into 0. So in the end, it compares 0 == 0.

This is the kind of behavior that makes == feel unpredictable, even though the engine is just following a fixed set of steps. It doesn’t care if the original types were different, as long as it can force them into something it can compare.

One last example that surprises a lot of people:

' \t\r\n' == 0     // true

That string is just whitespace, but when it gets coerced into a number, it turns into 0. So now it’s 0 == 0, which returns true. You wouldn’t normally think whitespace is equal to zero, but with ==, it ends up that way.

Why Reference Types Still Matter

With all the weirdness around coercion, you might think that == treats objects the same way. But it doesn’t. Two objects are only equal if they’re the same object in memory.

{} == {}           // false
[1, 2] == [1, 2] // false

Even if they look the same, these are different objects. They don’t point to the same place, so the comparison fails.

Now if you reuse a reference, it’s a different story.

let original = [1, 2];
let copy = original;
original == copy // true
original === copy // true

Both variables point to the same array, so both comparisons return true.

There’s another layer to this when objects get involved in comparisons with primitives. If one side is an object and the other is a primitive like a number or a string, then == will convert the object to a primitive first.

[1] == 1             // true
['1'] == 1 // true
[1,2] == '1,2' // true

In these cases, the arrays get turned into strings. For example, [1,2] becomes '1,2', so the final comparison is '1,2' == '1,2'.

It’s easy to fall into the trap of thinking == checks what something looks like, but that’s not really what’s going on. It’s checking what something turns into when coerced, and sometimes that means converting an array to a string or a string to a number before it compares the values.

Get Alexander Obregon’s stories in your inbox

Join Medium for free to get updates from this writer.

So when it comes to reference types, neither operator compares structure. They just ask, “are these the exact same object?” or, if coercion gets involved, “what do these become, and do those new values match?”

What Type Coercion Actually Does Step by Step

Type coercion is what JavaScript does when it needs to compare two values with == but sees that their types don’t match. Instead of returning false immediately, it tries to bring them to the same type first. This process follows a fixed set of internal steps. It doesn’t guess or improvise. It has a list of conversions it runs through to get both sides to a form where they can be compared. The language does this automatically, without asking for your permission or showing a warning.

General Type Coercion Rules

The first thing JavaScript checks when it sees a == b is whether the types match. If they do, it skips the rest and just checks the values. But if the types are different, it starts a conversion process. For example, if one side is a number and the other is a string, the string gets turned into a number.

42 == '42'        // true

In that line, the string '42' becomes the number 42, then it's just comparing 42 == 42, which returns true.

Booleans get converted into numbers as well. true becomes 1, and false becomes 0. So if you're comparing a boolean with a number, JavaScript first turns the boolean into a number and then checks the result.

true == 1         // true
false == 2 // false

In the first one, true becomes 1, and it ends up comparing 1 == 1. In the second one, false becomes 0, so it compares 0 == 2, which is false.

There’s a special rule for null and undefined. These two values are only loosely equal to each other and nothing else. That comparison returns true, but neither one is equal to 0, false, an empty string, or any object.

null == undefined   // true
null == 0 // false
undefined == false // false

That rule exists purely to treat null and undefined as interchangeable in some cases, but it doesn't extend to anything beyond that.

Another rule kicks in when comparing objects to primitive types like strings or numbers. In that case, JavaScript tries to convert the object into a primitive first, often using .valueOf() or .toString(), depending on the context.

[42] == '42'       // true

Here, [42] becomes '42' through its .toString() method, and then the comparison is '42' == '42'.

What Happens With Primitives and Objects

When JavaScript tries to coerce an object into a primitive, it doesn’t always take the same path. Arrays usually get turned into strings, and objects often fall back on their default value. You can think of it like this: if the left-hand side is an object and the right-hand side is a number or a string, JavaScript asks the object to give up a simpler version of itself.

Take this case:

[3] == 3           // true

The array [3] turns into the string '3', which is then converted into the number 3 before the comparison. So it becomes 3 == 3.

If the array had more than one item, the string result would be different.

[3, 4] == '3,4'    // true
[3, 4] == 3 // false

The array becomes the string '3,4', and that matches the string on the other side. In the second line, '3,4' doesn’t turn cleanly into a number, so it becomes NaN, and NaN == 3 is false.

Regular objects behave a little differently. If you compare an object to a string, JavaScript tries to call .toString() or .valueOf() to get a simpler version.

({ valueOf: () => 10 }) == 10     // true

The object’s valueOf() method returns 10, and that’s the value used in the comparison.

You can also see this behavior with custom objects that override their default methods.

let obj = {
toString() {
return '100';
}
};
obj == 100 // true

Here, the object turns into the string '100', which then becomes the number 100, so the result is true.

The coercion path gets chosen based on the type of value being compared. If it’s comparing to a number, JavaScript tries to produce a numeric result. If it’s comparing to a string, it looks for a string result instead. The actual conversion depends on the methods the object has and what they return.

Why === Avoids All This

Triple equals doesn’t trigger any of these conversions. If the types are different, it returns false right away and doesn’t try to change either value. That makes it easier to trust what you’re seeing.

'42' === 42        // false
[3] === 3 // false
false === 0 // false

All of these comparisons fail because the types don’t match. JavaScript doesn’t try to make them match. It leaves the values alone and just checks them as-is.

This is why === gives more predictable results. You know what it’s doing just by looking at the types. It doesn’t ask objects for a new version of themselves, and it doesn’t try to make strings into numbers. It’s a straight comparison with no background steps involved.

Conclusion

The reason == and === behave so differently comes down to what the JavaScript engine actually does behind the scenes. When you use ==, it runs through a set of type conversion steps to try to make the values match before comparing them. Sometimes that means turning an object into a string or a string into a number. It follows a strict internal order, but the results don’t always line up with what you'd expect just by reading the code. On the other hand, === skips all of that. It checks the type first, and if that doesn’t match, it stops there. Nothing gets converted, and no extra steps happen in the background. It checks exactly what’s written, which makes the comparison easier to read and reason about.

  1. JavaScript Abstract Equality Comparison (==)
  2. JavaScript Strict Equality Comparison (===)
  3. MDN Equality Comparisons and Sameness

Thanks for reading! If you found this helpful, highlighting, clapping, or leaving a comment really helps me out.

--

--

Alexander Obregon
Alexander Obregon

Written by Alexander Obregon

I post daily about programming topics and share what I learn as I go. For recaps, exclusive content, and to support me: https://alexanderobregon.substack.com