The Difference Between == and === in JavaScript
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 nullIt’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
{} === {} // falseBoth 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 // trueHere, 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 // falseIn 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 // trueYou’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 // trueThese 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 == [] // trueWhat’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 // trueThat 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] // falseEven 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 // trueBoth 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' // trueIn 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' // trueIn 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 // falseIn 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 // falseThat 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' // trueHere, [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 // trueThe 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 // falseThe 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 // trueThe 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 // trueHere, 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 // falseAll 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.
- JavaScript Abstract Equality Comparison (
==) - JavaScript Strict Equality Comparison (
===) - MDN Equality Comparisons and Sameness
Thanks for reading! If you found this helpful, highlighting, clapping, or leaving a comment really helps me out.





