== vs === vs Object.is() in JavaScript: What Actually Counts as Equal

You’d think equality would be the simplest operator in the language, and then you run [] == false and get true, or check NaN === NaN and get false for a value that’s supposed to represent itself. JavaScript actually has three separate ways to ask “are these the same,” and they genuinely disagree with each other often enough that picking the wrong one causes real bugs, not just style complaints.

The short version: == converts the operands to a matching type before comparing, === refuses to convert anything and compares type and value directly, and Object.is() behaves almost exactly like === except for two specific values where it disagrees on purpose.

Try It: Live Equality Tester

compared to
A == B
A === B
Object.is(A, B)

Loose Equality (==) Does Type Coercion First

When the two operands are different types, == converts one or both to a common type before comparing. Number-to-string comparisons convert the string to a number. Boolean comparisons convert the boolean to a number first (false becomes 0, true becomes 1), then that result gets compared again using the same rules. Objects get converted to primitives using their valueOf or toString methods before the comparison runs.

This is why [] == false is true: the empty array converts to the empty string, the empty string converts to 0, and false also converts to 0. Three silent conversions stacked on top of each other for one comparison.

Strict Equality (===) Skips Conversion Entirely

If the types don’t match, the answer is immediately false, no conversion attempted. If the types do match, it compares the values directly. For primitives that means comparing the actual value. For objects, arrays, and functions, it compares by reference, so two separately created objects with identical contents are never === to each other.

{ a: 1 } === { a: 1 }        // false, different objects in memory
const obj = { a: 1 };
obj === obj                   // true, same reference

Object.is(): Almost Like ===, With Two Exceptions

For nearly every value, Object.is(a, b) gives the same answer as a === b. The two places they diverge are worth knowing on purpose rather than discovering by accident.

NaN and -0 are the exceptions

NaN === NaN is false, because === follows the IEEE 754 floating point spec, where NaN is defined to never equal anything, including itself. But Object.is(NaN, NaN) is true, treating NaN as identical to itself the way you’d intuitively expect. Going the other direction, 0 === -0 is true, while Object.is(0, -0) is false, since they’re distinct values at the bit level even though arithmetic treats them the same.

In practice, this makes Object.is() the right choice specifically when you need to detect NaN reliably, such as validating a parsed number, or on the rare occasion the -0 vs 0 distinction actually matters to your logic. For everything else, === and Object.is() are interchangeable, and === is more familiar to read.

Coercion Reference Table

Comparison=====Object.is()
0 == falsetruefalsefalse
'' == falsetruefalsefalse
'0' == 0truefalsefalse
null == undefinedtruefalsefalse
NaN, NaNfalsefalsetrue
0, -0truetruefalse
[] == falsetruefalsefalse
[1,2] == '1,2'truefalsefalse

When to Use Which

Default to === for basically everything. It’s predictable, it doesn’t hide a chain of implicit conversions inside a single line, and it matches how most style guides and linters (ESLint’s eqeqeq rule included) expect code to be written.

There’s exactly one common exception worth knowing: checking for both null and undefined at once. value == null catches both in a single comparison, since null == undefined is true but neither equals anything else loosely. Some codebases allow this one specific case as a deliberate, narrow use of == rather than writing value === null || value === undefined every time.

Reach for Object.is() specifically when NaN or the -0 distinction is actually relevant, most commonly inside utility functions that need to detect NaN correctly, or in library code implementing something like a memoization cache where value identity has to be exact.

FAQ

Why is NaN === NaN false?

Because === follows the IEEE 754 floating point standard, which defines NaN as never equal to any value, including another NaN. It’s a deliberate part of the spec, not a JavaScript quirk.

How do I check if a value is NaN?

Use Number.isNaN(value), not the global isNaN(), which coerces its argument first and can return unexpected results for non-numbers. Object.is(value, NaN) also works correctly.

Is Object.is() the same as ===?

For all values except NaN and -0, yes, they behave identically. Those two cases are the entire reason Object.is() exists as a separate method.

Should I ever use == in modern JavaScript?

Rarely. The one commonly accepted case is value == null to check for both null and undefined at once. Outside that, === is the safer default and most linters will flag == by default.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top