JavaScript Has 9 Ways to Check Equality (And They All Disagree)
JS Edition
March 18, 2026

Most languages have one way to check if two things are equal. Maybe two if you’re fancy.
JavaScript has nine.
And they all give different answers. On the same values.
Let me show you the madness.
The Basics (That Aren’t Basic)
Everyone knows about == and ===. Right?
5 == "5" // true (type coercion)\n5 === "5" // false (strict)
Simple enough. Use ===, avoid ==, move on with your life.
But then there’s this:
NaN === NaN // false\nObject.is(NaN, NaN) // true\n\n-0 === 0 // true\nObject.is(-0, 0) // false
So === lies about two things:
-
NaN is not equal to itself (except it is)
-
Negative zero equals positive zero (except it doesn’t)
And Object.is() tells the truth. Except most of the time you want the lie.
This is fine. Everything is fine.
Wait, There Are NINE?
Here’s the full list of equality algorithms in JavaScript:
-
==(Abstract Equality) - “Whatever, make it work” -
===(Strict Equality) - “Types must match (but I lie about NaN and -0)” -
Object.is()- “Actual equality” -
SameValueZero - “Like Object.is but 0 equals -0”
-
SameValue - “Like Object.is (it’s the same thing)”
-
<and>- “Relational comparison with coercion” -
<=and>=- “Wait these work differently” -
inoperator - “Does property exist?” -
instanceof- “Prototype chain checking”
You don’t use #4, #5, #8, and #9 directly. JavaScript uses them internally. But they exist. And they disagree with each other.
Let me prove it.
The Comparison Chart From Hell
const x = NaN;\nconst y = NaN;\n\nx == y // false\nx === y // false\nObject.is(x, y) // true
So are NaN values equal or not? Depends which algorithm you ask.
const a = 0;\nconst b = -0;\n\na == b // true\na === b // true\nObject.is(a, b) // false
Are zeros equal? Also depends.
const arr = [NaN];\n\narr.includes(NaN) // true (uses SameValueZero)\narr.indexOf(NaN) // -1 (uses ===)\narr.find(x => x) // NaN (you make the rules)
So includes() finds NaN, but indexOf() doesn’t. Same array. Same value. Different results.
This makes sense to exactly zero people.
The Type Coercion Disaster
Let’s talk about ==. The comparison operator that tries to be helpful and fails spectacularly.
0 == false // true\n0 == "" // true\n0 == "0" // true\nfalse == "" // true\nfalse == "0" // true\n\n// But wait...\n"" == "0" // false (???)
So by the transitive property of... wait, there is no transitive property.
If A equals B, and B equals C, does A equal C?
Not in JavaScript!
// A == B\n"" == 0 // true\n\n// B == C\n0 == "0" // true\n\n// Therefore A == C?\n"" == "0" // false\n\n// Math has left the chat
The Array Comparison Minefield
[] == [] // false (different objects)\n[] == ![] // true (I'm sorry, what?)
Let me break down that second one:
-
![]converts tofalse(arrays are truthy, so!makes it falsy) -
[] == falsecompares empty array to false -
Empty array converts to empty string
"" -
Empty string converts to
0 -
falseconverts to0 -
0 == 0is true
So [] == ![] is true because of a five-step type coercion chain.
This is why we can’t have nice things.
When JavaScript Uses Which Algorithm
Here’s what’s actually happening under the hood:
=== (Strict Equality):
-
Most comparisons you write
-
switchstatements -
Some array methods
SameValueZero:
-
Array.includes() -
Mapkeys -
Setvalues -
String.includes()
SameValue (aka Object.is()):
-
Object.defineProperty() -
Internal slot comparisons
-
Spec algorithms
Abstract Equality (==):
-
Your tech debt from 2012
-
Legacy code you’re afraid to touch
-
That one comparison you regret
Wait, what’s the difference between SameValue and SameValueZero?
// SameValue: -0 and 0 are different\nObject.is(-0, 0) // false\n\n// SameValueZero: -0 and 0 are same\n[-0].includes(0) // true (uses SameValueZero)
One treats -0 and 0 as different. The other treats them as the same.
That’s the only difference. That’s why we have two separate algorithms.
Because apparently one algorithm that handles negative zero wasn’t complicated enough.
The Real-World Impact
I’ve been coding JavaScript since the jQuery days. Here’s when these differences actually matter:
Never:
-
SameValue vs SameValueZero differences
-
Comparison algorithm implementation details
-
The existence of
==(just don’t use it)
Rarely:
-
NaN comparisons (use
Number.isNaN()) -
-0 comparisons (pretend it doesn’t exist)
-
Object.is()for anything
Always:
- Just use
===and move on
The one exception:
// Checking if something is NaN\nObject.is(value, NaN) // Works\nvalue !== value // Also works (NaN is the only value not equal to itself)\nNumber.isNaN(value) // Best option
Let’s Test Your Understanding
What do these return?
// Test 1\nnull == undefined\n\n// Test 2\nnull === undefined\n\n// Test 3\n[1, 2, 3] == [1, 2, 3]\n\n// Test 4\n[1, 2, 3] === [1, 2, 3]\n\n// Test 5\ntypeof NaN === "number"
Answers:
-
true-==considers null and undefined equal -
false-===doesn’t -
false- Arrays are objects, compared by reference -
false- Still comparing references -
true- NaN is technically a number (even though it means “Not a Number”)
If you got all five right, you either:
-
Read the spec for fun
-
Have been traumatized by bugs
-
Are lying
The Type Coercion Table You’ll Never Memorize
Here’s what == does when comparing different types:
undefined == null // true (special case)\nnumber == string // Convert string to number\nboolean == anything // Convert boolean to number (true=1, false=0)\nobject == primitive // Call valueOf() or toString()
This creates gems like:
true == "1" // true (boolean→1, "1"→1)\ntrue == "2" // false (boolean→1, "2"→2)\n"0" == false // true ("0"→0, false→0)\n"00" == false // true ("00"→0, false→0)\n"0" == "00" // false (both strings, compared directly)
The transitive property is weeping.
Objects Are Never Equal (Except When They Are)
const obj1 = {a: 1};\nconst obj2 = {a: 1};\n\nobj1 == obj2 // false\nobj1 === obj2 // false
Objects compare by reference, not value. Different objects are never equal.
const obj3 = obj1;\n\nobj1 === obj3 // true (same reference)
But wait:
const obj4 = Object.create(null);\nconst obj5 = Object.create(null);\n\nobj4 == obj5 // false (still different objects)
Even objects with no prototype aren’t equal.
The only way objects are equal is if they’re literally the same object.
Which makes sense, but then you see this:
[] == "" // true\n[1] == "1" // true\n[1,2] == "1,2" // true
Arrays convert to strings for comparison. So arrays can equal strings. But two identical arrays aren’t equal to each other.
[1] == [1] // false (different objects)\n[1] == "1" // true (converts to string)
Logic is not JavaScript’s strong suit.
The Philosophical Question
Is NaN equal to NaN?
IEEE 754 says: No. NaN represents “undefined” or “unrepresentable” values. Two undefined values aren’t necessarily the same undefined value.
JavaScript says: Depends which function you ask.
NaN === NaN // false\nObject.is(NaN, NaN) // true\n[NaN].includes(NaN) // true\n[NaN].indexOf(NaN) // -1
So NaN is not equal to itself, except when it is, and you can find it in an array, except when you can’t.
My philosophy: NaN is JavaScript’s way of telling you “something went wrong, but I’m not going to tell you what.”
It’s the blue screen of death, but for numbers.
Practical Advice (Finally)
After all this madness, here’s what you actually do:
1. Use === for everything
if (x === y) {\n // Works 99.9% of the time\n}
2. Check for NaN specifically
if (Number.isNaN(x)) {\n // The right way\n}\n\n// Not this:\nif (x !== x) {\n // Clever but confusing\n}
3. Avoid == like it’s cursed
Because it is.
if (x == y) {\n // Code review comment: "pls use ==="\n}
4. For objects, use a library
import { isEqual } from 'lodash';\n\nisEqual(obj1, obj2); // Deep equality
5. Forget everything else exists
You don’t need SameValueZero. You don’t need SameValue. You don’t need to know they exist.
Unless you’re reading this article. In which case, sorry, you know now.
The Mental Model
Think of JavaScript’s equality like this:
-
==: “These are kinda the same if you squint” -
===: “These are exactly the same (except NaN and -0)” -
Object.is(): “These are actually exactly the same” -
Everything else: “Internal spec stuff you shouldn’t worry about”
Or, simplified:
Just use === and be mildly confused sometimes.
The Final Boss
Okay, last test. What does this return?
const weird = {\n valueOf() {\n return 3;\n },\n toString() {\n return "3";\n }\n};\n\nweird == 3;
Think about it.
The answer is: true
When comparing an object to a primitive with ==, JavaScript calls valueOf() first. The object becomes 3, which equals 3.
But:
weird === 3; // false
Because === doesn’t do type coercion. The object stays an object.
This is why we don’t use ==.
How Other Languages Handle This
Python:
# Just two ways\nx == y # Equality\nx is y # Identity (same object)
Simple. Sane. Boring.
Ruby:
x == y # Value equality\nx.eql?(y) # Hash equality\nx.equal?(y) # Object identity
Three ways. Reasonable.
Java:
x == y // Reference equality\nx.equals(y) // Value equality
Two ways. Clear purpose for each.
JavaScript:
Nine ways. Complete chaos. Choose your own adventure. Nothing makes sense. Welcome to hell.
My Honest Take
JavaScript’s equality is a disaster because of two design decisions in 1995:
-
==tries to be helpful - Type coercion seemed like a good idea at the time. It wasn’t. -
IEEE 754 compliance - NaN and -0 behave mathematically correctly but practically confusingly.
These decisions made sense in isolation. Together, they created a Gordian knot of comparison operators that trip up beginners and experts alike.
The solution: Pretend only === exists. Use Number.isNaN() for NaN. Ignore -0. Use lodash’s isEqual() for objects.
Don’t think about the other six algorithms. They can’t hurt you if you don’t use them.
Mostly.
Try it yourself:
Open your console and run:
// The trilogy of confusion\nconsole.log(NaN === NaN); // false\nconsole.log([] == ![]); // true \nconsole.log("" == "0"); // false\nconsole.log(0 == ""); // true\nconsole.log(0 == "0"); // true\n\n// Bonus round\nconsole.log(typeof NaN); // "number"\nconsole.log(NaN !== NaN); // true\nconsole.log([NaN].includes(NaN)); // true\nconsole.log([NaN].indexOf(NaN)); // -1\n\n// You can never unknow this
Other JavaScript crimes:
-
typeof null === "object" -
[] + [] === "" -
[] + {} !== {} + [] -
Math.max() < Math.min()
The language is held together by duct tape and bad decisions from 1995.
Which JavaScript comparison broke your code? Hit reply with your war stories.
Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.