LEARN · DEBUGGING GUIDE

Debugging JavaScript NaN Comparison: Why NaN === NaN is False

NaN is the only JavaScript value not equal to itself. Any comparison with NaN — even NaN === NaN — evaluates to false. Here's how to find and fix it.

BeginnerJavaScript6 min read

What this usually means

You're comparing a value directly to NaN using ===, ==, or similar operators. By specification, NaN is not equal to any value, including itself. This is by design, because NaN represents an indeterminate numeric result (like 0/0). JavaScript enforces IEEE 754 floating point behavior where NaN ≠ NaN. The fix is to use Number.isNaN() or Object.is() instead.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1console.log(NaN === NaN); // false — confirm the behavior immediately
  • 2In the browser console, type 0/0 and then check 0/0 === 0/0
  • 3Identify all equality checks (===, ==, !==, !=) against NaN in your codebase using grep: grep -rn '=== NaN\|== NaN' src/
  • 4Check for indexOf uses: grep -rn 'indexOf(NaN')' src/ — indexOf uses strict equality
  • 5Test with Number.isNaN() on the suspected variable: Number.isNaN(yourVar)
  • 6Use Object.is(yourVar, NaN) to confirm if the value is NaN
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchAll files with equality checks against NaN (search for '=== NaN', '== NaN')
  • searchArray.indexOf or Array.includes calls — they use strict equality internally
  • searchSet.has() and Map.has() calls — also use SameValueZero but note that NaN is treated as equal to NaN in Set/Map (ES6). Check if you're on an older JS engine or polyfill
  • searchSwitch statement cases with NaN
  • searchJSON.stringify output — NaN becomes null, check if that's causing issues
  • searchCalculator or math-heavy functions that might produce NaN from edge cases like Math.sqrt(-1)
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningDirect equality check: if (x === NaN) { ... }
  • warningUsing indexOf to find NaN in an array
  • warningSwitch statement with case NaN
  • warningNot checking for NaN before mathematical operations
  • warningAssuming NaN behaves like other falsy values in comparisons
  • warningUsing isNaN() instead of Number.isNaN() — isNaN coerces non-numbers to NaN, leading to false positives
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildReplace x === NaN with Number.isNaN(x) or Object.is(x, NaN)
  • buildTo check if a value is not NaN, use !Number.isNaN(x) instead of x !== NaN (which is always true)
  • buildFor array inclusion, use Array.prototype.includes(NaN) — includes uses SameValueZero and correctly identifies NaN
  • buildFor indexOf, write your own helper: arr.some(item => Number.isNaN(item))
  • buildFor Set/Map, they handle NaN correctly in modern JS (ES6), but if using a polyfill, check its implementation
  • buildAdd a guard clause at the start of math functions: if (Number.isNaN(result)) { /* handle */ }
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedWrite a unit test that asserts Number.isNaN(x) returns true for NaN values
  • verifiedTest array includes: assert([NaN].includes(NaN) === true)
  • verifiedTest Set.has: assert(new Set([NaN]).has(NaN) === true)
  • verifiedIn production, add console.warn when NaN is detected to log the call stack
  • verifiedUse static analysis tools (ESLint rule: use-isnan) to catch direct comparisons
  • verifiedRun a quick REPL test: Object.is(NaN, NaN) // true
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningUsing isNaN() instead of Number.isNaN() — isNaN('abc') returns true (type coercion)
  • warningAssuming x !== NaN is a valid check for non-NaN — it's always true because NaN never equals anything
  • warningNot handling NaN in conditional chains: if (x) won't catch NaN because NaN is falsy, but it's not the same as null/undefined
  • warningWriting custom isNaN polyfills incorrectly — many legacy polyfills have bugs with coercion
  • warningForgetting that parseFloat('abc') returns NaN, and then comparing that result with === NaN
  • warningUsing jQuery.inArray() or similar utilities that rely on === internally
( 07 )War story

The Missing NaN in the Shopping Cart Total

Frontend DeveloperReact, Redux, JavaScript ES6

Timeline

  1. 09:15User reports that applying a coupon code 'FREESHIP' doesn't update the total
  2. 09:22Developer recreates bug: total stays at $0.00 after coupon applied
  3. 09:30Checks Redux DevTools: action dispatched with coupon, but total remains 0
  4. 09:40Reducer logs: discountPercent is NaN after parsing coupon
  5. 09:45Finds line: if (discountPercent === NaN) return state; — always false, so NaN passes through
  6. 09:50Realizes parseFloat('FREESHIP') returns NaN, but comparison fails
  7. 09:55Fix: if (Number.isNaN(discountPercent)) return state;
  8. 10:00Deploys hotfix, total now updates correctly when coupon is invalid

I was on call when a support ticket came in: a user applied the coupon 'FREESHIP' but the cart total stayed at $0.00. The coupon was supposed to be a percentage discount, but it wasn't applied. I reproduced it on staging immediately. The Redux DevTools showed the action dispatched correctly with coupon code 'FREESHIP', but the total in the reducer never changed.

I logged into the reducer and saw that discountPercent was NaN. The coupon parser used parseFloat('FREESHIP'), which returns NaN because the string isn't numeric. But then I saw the guard: if (discountPercent === NaN) return state; — that was the bug. That condition is always false, so the NaN value passed through and broke the calculation.

I fixed it by replacing the equality check with Number.isNaN(discountPercent). Then I added a unit test for invalid coupon codes. After deploying, the coupon correctly shows an error message instead of silently failing. I also ran a grep for all '=== NaN' patterns in the codebase and found three other instances that I fixed immediately.

Root cause

Direct comparison to NaN using ===, which always returns false.

The fix

Replaced discountPercent === NaN with Number.isNaN(discountPercent).

The lesson

Never compare directly to NaN. Use Number.isNaN() or Object.is(). Also, treat NaN as a first-class error state.

( 08 )Why NaN !== NaN by Specification

JavaScript follows the IEEE 754 floating-point standard, which dictates that NaN is not equal to itself. This is because NaN represents an indeterminate result, and there can be multiple representations of NaN (signaling vs. quiet). The standard states that any comparison involving NaN returns false, except for the 'unordered' predicate.

In practice, this means: NaN === NaN → false, NaN == NaN → false, and even Object.is(NaN, NaN) returns true only because Object.is explicitly treats NaN as equal. The only way to reliably check for NaN is Number.isNaN() (ES6) or the older global isNaN() with its coercion caveats.

( 09 )The Flaw in Using isNaN() vs Number.isNaN()

The global isNaN() function first coerces the argument to a number. For example, isNaN('foo') returns true because Number('foo') is NaN. This can lead to false positives when checking non-numeric strings. In contrast, Number.isNaN() does not coerce; it returns true only if the value is exactly NaN.

If you need to check if a value is NaN without risking coercion, always use Number.isNaN(). For older browsers, a polyfill is: Number.isNaN = Number.isNaN || function(value) { return typeof value === 'number' && isNaN(value); }.

( 10 )Array Methods and NaN: indexOf vs includes

Array.prototype.indexOf uses strict equality (===) internally. So [NaN].indexOf(NaN) returns -1. This is a common pitfall when checking if an array contains NaN. The fix is to use Array.prototype.includes, which uses the SameValueZero algorithm and correctly identifies NaN as equal to itself.

Similarly, Set.prototype.has and Map.prototype.has use SameValueZero, so they work correctly with NaN. But be aware that older polyfills might not follow the spec. Always test your target environments.

( 11 )Static Analysis and Linting

ESLint has a built-in rule 'use-isnan' that catches direct comparisons to NaN. It will flag if (x === NaN) and suggest using Number.isNaN(x). Enable this rule in your .eslintrc: { 'rules': { 'use-isnan': 'error' } }.

Additionally, consider using TypeScript, which has a compile-time check for comparisons with NaN. TypeScript will emit an error like 'This condition will always return false' for x === NaN.

( 12 )Edge Cases: NaN in JSON and Serialization

JSON.stringify converts NaN to null. This can cause silent data corruption if you're sending data to an API. For example, JSON.stringify({price: NaN}) becomes '{"price":null}'. When the API receives null, it might cause unexpected behavior.

To handle NaN in serialization, you can implement a custom replacer in JSON.stringify: JSON.stringify(value, (key, val) => Number.isNaN(val) ? 'NaN' : val). Or use a library like 'json5' that supports NaN.

Frequently asked questions

Why does NaN === NaN return false?

Because JavaScript follows the IEEE 754 standard, which says that NaN is not equal to any value, including itself. This is because NaN can represent many different 'not a number' states, and equality would be meaningless.

How do I check if a value is NaN in JavaScript?

Use Number.isNaN(value). This function returns true only if the value is exactly NaN and does not coerce non-numbers. Avoid using the global isNaN() because it coerces values (e.g., isNaN('abc') returns true).

Does Array.includes work with NaN?

Yes. Array.prototype.includes uses the SameValueZero algorithm, which treats NaN as equal to itself. So [NaN].includes(NaN) returns true. In contrast, indexOf uses strict equality and returns -1 for NaN.

Can I use switch with NaN?

No. Switch statements use strict equality (===) for comparison, so case NaN: will never match. Instead, use if-else with Number.isNaN().

What is the difference between isNaN() and Number.isNaN()?

The global isNaN() coerces the argument to a number before checking. For example, isNaN('hello') returns true because Number('hello') is NaN. Number.isNaN() does not coerce; it returns true only if the value is exactly NaN. Use Number.isNaN() for reliable checks.