JavaScript Equality Table

ExpressionResultWhy
1 == '1'true== coerces types before comparing - the source of endless bugs
1 === '1'false=== compares type first; different types are never equal
null == undefinedtrueThe one special pair == treats as equal
null === undefinedfalseDifferent types - strict never bends
NaN === NaNfalseNaN is not equal to anything, including itself - use Number.isNaN
0 == ''trueBoth coerce to 0 - the empty string trap
[] == falsetrueEmpty array coerces to empty string, then to 0
Object.is(NaN, NaN)trueThe third comparison - SameValue, stricter than ===
Reference: the MDN strict equality reference. The team rule that ends the debate: always ===, never == (the one accepted exception is x == null as an idiomatic "null or undefined" check). The rows above are the coercion gallery - == converts before comparing, so type errors hide until production. Bottom line: === plus Number.isNaN covers 99 percent of comparisons; Object.is exists for the NaN edge case that === cannot see. Related tools: JS error table (the TypeError these hide), classes table (instanceof vs equality), and loops table for where the coercions sneak in.

JavaScript has three equality operators - ==, ===, and Object.is - and they disagree on purpose. The loose operator converts before comparing; the strict operator refuses to convert; Object.is applies the strictest definition including how NaN treats itself. The table below is the gallery of results that decide which one your code should use.

Bottom line: the team rule that ends the debate is always ===, never == - with one idiomatic exception, x == null as shorthand for "null or undefined". The loose operator's coercion table is the source of the language's most famous surprises: 0 == '' is true, [] == false is true, and '1' == 1 hides a type error until production. Strict comparison makes those bugs compile-time visible instead.

The honest part: === has exactly one blind spot - NaN is not equal to itself, by IEEE 754 design (NaN means "not a number", and two unknowns are not the same unknown). That is why Number.isNaN exists for detection and Object.is for comparison: Object.is(NaN, NaN) is true, making it the SameValue semantics the language uses internally for things like array index changes in frameworks.

How to use

  1. Default to === in all code; reach for the table when you meet a == result you did not expect.
  2. Use Number.isNaN(x) to detect NaN - x === NaN is always false, the only value with that property.
  3. Know the one idiomatic == : x == null is the compact "null or undefined" test; write it longhand if your style guide prefers.

Frequently asked questions

What is the actual difference between == and ===?

== converts both sides to a common type before comparing ("1" == 1 is true because the string becomes a number); === compares types first and returns false immediately for different types. Every surprise in the table - 0 == '', [] == false, null == undefined - is the conversion rules at work. === has exactly two special cases of its own: +0 and -0 are equal, and NaN is not. Once types match, the two operators agree.

Why does NaN not equal NaN?

Because NaN means "an unspecified invalid number" - and IEEE 754, the floating-point standard JavaScript follows, defines that two unspecified values cannot be claimed equal. The practical consequence: x === NaN is always false even when x IS NaN, which is why the language provides Number.isNaN(x) for detection. The deeper point: NaN usually arrives as the result of a failed parse or 0/0 - the right fix is usually upstream, not a cleverer comparison.

Is x == null acceptable if I always use ===?

It is the one widely accepted exception: == null is exactly true for null and undefined and false for everything else - including 0 and empty string, which == null would wrongly accept in its looser moments. It works because null and undefined are the only pair the loose operator special-cases. Many style guides still require the explicit x === null || x === undefined; the compact form is not wrong, it is a documented idiom - pick one and lint for it.

When do I need Object.is instead of ===?

Two places: NaN comparison - Object.is(NaN, NaN) is true where === is false - and signed zero - Object.is(0, -0) is false where === is true. React's dependency comparison uses Object.is semantics for exactly these edge cases, which is why a state update with NaN behaves differently from one with a normal number. Outside framework internals and numeric code that distinguishes signed zero, === plus Number.isNaN covers everything Object.is does with less vocabulary.

Related tools