JavaScript Equality: ==, === and Object.is Compared
Why 0 == "" is true, NaN !== NaN, and when Object.is differs from strict equality. A clear model for JavaScript comparison instead of memorised rules.
Table of contents
- Strict equality (===) compares without conversion
- Loose equality (==) coerces first
- Object.is and the two values === gets wrong
- Comparing objects by value
- Frequently asked questions
- Is === always faster than ==?
- Why is typeof null "object"?
- How should I check whether an array is empty?
- Does === work for comparing dates?
- Related reading
- References
JavaScript has three equality operations, and the usual advice — "always use ===" — is correct but incomplete. Knowing why prevents the small class of bugs where === is also wrong.
Strict equality (===) compares without conversion#
=== returns true when both operands have the same type and the same value. No coercion happens.
1 === 1; // true
1 === '1'; // false — different types
null === undefined; // falseFor objects it compares identity, not contents:
{ a: 1 } === { a: 1 } // false — two different objects
[1, 2] === [1, 2] // false
const shared = { a: 1 };
shared === shared // trueThis is why array.includes(someObject) usually fails: it uses strict equality, so it only matches the exact same object reference.
Loose equality (==) coerces first#
== converts operands to a common type before comparing. The algorithm is well-defined but produces results nobody predicts:
0 == '' // true
0 == '0' // true
'' == '0' // false ← not transitive!
null == undefined // true
[] == false // true
[1] == 1 // trueThe ''/'0'/0 triple is the clearest demonstration of the problem: == is not transitive, so you cannot reason about it locally.
There is exactly one case where == is genuinely useful:
if (value == null) {
// true for null AND undefined, false for 0, '', false, NaN
}That is a common enough intent that many style guides carve out this single exception. Everything else should be ===.
Object.is and the two values === gets wrong#
=== has two documented quirks:
NaN === NaN; // false
0 === -0; // trueObject.is fixes both:
Object.is(NaN, NaN); // true
Object.is(0, -0); // falseFor everything else it behaves identically to ===.
When does this matter in practice? NaN most often. If you need to know whether a computed value is NaN, use Number.isNaN(value) — and note that the global isNaN is a different, coercing function:
Number.isNaN('hello'); // false — it is a string, not NaN
isNaN('hello'); // true — coerces to NaN first. Avoid.Negative zero matters rarely, but it is real: it comes out of Math.round(-0.2) and of division by -Infinity, and Object.is is the only way to detect it apart from 1 / value === -Infinity.
Comparing objects by value#
Since === compares identity, structural comparison needs a strategy. In order of preference:
Compare the fields you care about. Usually there is an id.
const same = a.id === b.id;Use the built-in, if available. Modern runtimes ship a deep-equality helper in Node (util.isDeepStrictEqual); browsers do not.
Serialise, carefully. JSON.stringify(a) === JSON.stringify(b) works for plain data but is wrong in ways worth knowing: key order matters, undefined values and functions are dropped, NaN and Infinity become null, and Date objects become strings. It is a reasonable shortcut in a test, not in production logic.
Frequently asked questions#
Is === always faster than ==?#
Marginally, since it can skip coercion, but the difference is unmeasurable in real code. Choose === for correctness, not speed.
Why is typeof null "object"?#
A bug from JavaScript's first implementation that has been kept for backwards compatibility. It means typeof value === 'object' is true for null, so a null check has to come first: if (value !== null && typeof value === 'object').
How should I check whether an array is empty?#
array.length === 0. Do not use array == false — it is true for an empty array, which looks like it works, and also true for [0], which does not.
Does === work for comparing dates?#
No. Two Date objects for the same instant are different objects, so === is false. Compare their numeric values: a.getTime() === b.getTime(), or +a === +b.
Related reading#
- JavaScript Array Methods — where
includesand identity comparison collide - TypeScript Strict Mode — how the type system removes most of this class of bug
- Comparing two blobs of text or JSON? The Text Diff Checker is faster than eyeballing it.