Skip to content

Fix: JavaScript NaN === NaN Returns false (NaN Comparison Bugs)

FixDevs ·

Quick Answer

How to fix JavaScript NaN comparison bugs — why NaN !== NaN, the difference between isNaN() and Number.isNaN(), and how to correctly check for NaN in conditionals and array operations.

The Error

You check if a value is NaN using === and the condition never matches:

const result = parseInt("hello");
console.log(result);         // NaN
console.log(result === NaN); // false — NaN is never equal to anything, including itself
console.log(result == NaN);  // false — same issue
console.log(NaN === NaN);    // false — this is the core quirk

Or a validation function always passes even for invalid numbers:

function isValidNumber(value) {
  return value !== NaN; // Always true — NaN !== NaN is always true!
}

isValidNumber(NaN);    // true — wrong, NaN should be invalid
isValidNumber(42);     // true — correct
isValidNumber("abc");  // true — correct for this check, but NaN would also pass

Or Array.includes() or Array.indexOf() fails to find NaN:

const arr = [1, NaN, 3];
arr.includes(NaN);     // true  — includes uses SameValueZero
arr.indexOf(NaN);      // -1   — indexOf uses strict equality (NaN !== NaN)

Why This Happens

NaN (Not a Number) is a special IEEE 754 floating-point value that represents an undefined or unrepresentable numeric result. It has one unusual property: it is not equal to any value, including itself.

This is defined by the IEEE 754 standard and implemented consistently across all programming languages that use it:

NaN === NaN  // false — by specification
NaN !== NaN  // true  — the only value where this holds

NaN arises from:

parseInt("hello")         // NaN — cannot parse
parseFloat("abc")         // NaN
Number("text")            // NaN
0 / 0                     // NaN
Math.sqrt(-1)             // NaN
undefined + 1             // NaN
NaN + 1                   // NaN — any operation with NaN produces NaN

Fix 1: Use Number.isNaN() for Reliable NaN Detection

Number.isNaN() (ES2015+) is the correct way to check for NaN:

Number.isNaN(NaN);         // true  ✓
Number.isNaN(42);          // false ✓
Number.isNaN("hello");     // false ✓ — string is not NaN (it's not even a number)
Number.isNaN(undefined);   // false ✓
Number.isNaN(null);        // false ✓
Number.isNaN(Infinity);    // false ✓

Fix the validation function:

// Broken
function isValidNumber(value) {
  return value !== NaN; // Always true
}

// Fixed
function isValidNumber(value) {
  return typeof value === "number" && !Number.isNaN(value);
}

isValidNumber(NaN);    // false ✓
isValidNumber(42);     // true  ✓
isValidNumber("42");   // false ✓ — string, not a number
isValidNumber(Infinity); // true — Infinity is a valid number value; add check if needed

Why this works: Number.isNaN() only returns true if the value is both of type number and has the NaN value. Unlike the global isNaN(), it does not coerce its argument — it strictly checks for the NaN value.

Fix 2: Understand the Difference Between isNaN() and Number.isNaN()

The global isNaN() function coerces its argument to a number before checking — this causes false positives:

// Global isNaN() — coerces to number first
isNaN(NaN);        // true  ✓
isNaN("hello");    // true  — "hello" coerced to NaN, then checked → true (misleading!)
isNaN(undefined);  // true  — undefined coerced to NaN → true (misleading!)
isNaN(null);       // false — null coerced to 0 → false (surprising!)
isNaN("42");       // false — "42" coerced to 42 → false

// Number.isNaN() — no coercion, strict check
Number.isNaN(NaN);        // true  ✓
Number.isNaN("hello");    // false ✓ — string is not NaN value
Number.isNaN(undefined);  // false ✓ — undefined is not NaN value
Number.isNaN(null);       // false ✓
Number.isNaN("42");       // false ✓

Common mistake with global isNaN():

function processInput(userInput) {
  if (isNaN(userInput)) {
    throw new Error("Not a number");
  }
  return Number(userInput) * 2;
}

processInput("hello");    // Correctly throws
processInput(undefined);  // Incorrectly throws — undefined is a valid JS value
processInput("  ");       // Incorrectly throws — empty string coerces to 0

Fixed with Number.isNaN():

function processInput(userInput) {
  const num = Number(userInput);
  if (Number.isNaN(num)) {
    throw new Error("Input cannot be converted to a number");
  }
  return num * 2;
}

Fix 3: Check for NaN After Numeric Conversions

Whenever you convert user input or external data to a number, always check for NaN:

// Parse and validate safely
function safeParseInt(value, defaultValue = 0) {
  const parsed = parseInt(value, 10); // Always specify radix
  return Number.isNaN(parsed) ? defaultValue : parsed;
}

safeParseInt("42");      // 42
safeParseInt("hello");   // 0 (defaultValue)
safeParseInt("3.14");    // 3 (parseInt truncates)
safeParseInt(undefined); // 0

// Parse float with validation
function safeParseFloat(value, defaultValue = 0) {
  const parsed = parseFloat(value);
  return Number.isNaN(parsed) ? defaultValue : parsed;
}

safeParseFloat("3.14");  // 3.14
safeParseFloat("$9.99"); // 9.99 — parseFloat reads until non-numeric
safeParseFloat("abc");   // 0 (defaultValue)

Validate form input:

function validateAge(input) {
  const age = Number(input);

  if (Number.isNaN(age)) {
    return { valid: false, error: "Age must be a number" };
  }
  if (!Number.isFinite(age)) {
    return { valid: false, error: "Age must be a finite number" };
  }
  if (age < 0 || age > 150) {
    return { valid: false, error: "Age must be between 0 and 150" };
  }
  return { valid: true, value: age };
}

validateAge("25");     // { valid: true, value: 25 }
validateAge("abc");    // { valid: false, error: "Age must be a number" }
validateAge(Infinity); // { valid: false, error: "Age must be a finite number" }

Fix 4: Fix NaN in Array Operations

Array.indexOf() uses strict equality (===) and cannot find NaN. Use Array.includes() or Array.findIndex() instead:

const values = [1, NaN, 3, NaN, 5];

// indexOf — cannot find NaN
values.indexOf(NaN);           // -1 — wrong

// includes — uses SameValueZero algorithm (handles NaN)
values.includes(NaN);          // true ✓

// findIndex — use Number.isNaN as predicate
values.findIndex(Number.isNaN); // 1 ✓ — index of first NaN

// filter — remove NaN values
const cleanValues = values.filter(v => !Number.isNaN(v));
// [1, 3, 5]

// Count NaN values
const nanCount = values.filter(Number.isNaN).length; // 2

Sorting arrays containing NaN:

const nums = [3, NaN, 1, NaN, 2];

// Default sort treats NaN as a string "NaN" — produces unpredictable results
nums.sort((a, b) => a - b);
// [1, 2, 3, NaN, NaN] — NaN goes to the end (implementation-dependent)

// Explicit NaN handling in sort
nums.sort((a, b) => {
  if (Number.isNaN(a)) return 1;  // Move NaN to end
  if (Number.isNaN(b)) return -1;
  return a - b;
});
// [1, 2, 3, NaN, NaN]

Fix 5: Fix NaN Propagation in Calculations

NaN is contagious — any arithmetic operation with NaN produces NaN. A single NaN in a chain of calculations poisons all downstream results:

const price = parseFloat("$29.99");  // NaN — parseFloat stops at "$"
const tax = price * 0.1;             // NaN — NaN * 0.1 = NaN
const total = price + tax;           // NaN — NaN + NaN = NaN
const formatted = total.toFixed(2);  // "NaN"

Fix — validate at the input boundary:

function parsePrice(input) {
  // Remove currency symbols before parsing
  const cleaned = String(input).replace(/[^0-9.]/g, "");
  const price = parseFloat(cleaned);
  if (Number.isNaN(price)) {
    throw new Error(`Invalid price: ${input}`);
  }
  return price;
}

const price = parsePrice("$29.99"); // 29.99 ✓
const tax = price * 0.1;            // 2.999 ✓
const total = price + tax;          // 32.989 ✓

Guard calculations with default values:

function safeDivide(a, b) {
  if (b === 0 || Number.isNaN(a) || Number.isNaN(b)) return 0;
  return a / b;
}

safeDivide(10, 2);   // 5
safeDivide(10, 0);   // 0 — instead of Infinity
safeDivide(NaN, 5);  // 0 — instead of NaN

Fix 6: Fix NaN in Object Comparisons and JSON

NaN in JSON is problematic — JSON does not have a NaN value:

JSON.stringify({ value: NaN });  // '{"value":null}' — NaN becomes null!
JSON.parse('{"value":null}');    // { value: null } — not NaN

// Round-trip loses NaN
const obj = { score: NaN };
const json = JSON.stringify(obj);      // '{"score":null}'
const parsed = JSON.parse(json);       // { score: null }
parsed.score === obj.score;            // false — null !== NaN

Replace NaN before serializing:

function serializeSafely(obj) {
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === "number" && Number.isNaN(value)) {
      return null; // or undefined to omit the key, or a sentinel string
    }
    return value;
  });
}

TypeScript — catch NaN at compile time with branded types:

type ValidNumber = number & { readonly __brand: "ValidNumber" };

function toValidNumber(value: unknown): ValidNumber {
  const n = Number(value);
  if (Number.isNaN(n) || !Number.isFinite(n)) {
    throw new Error(`Cannot convert ${value} to ValidNumber`);
  }
  return n as ValidNumber;
}

function calculateTotal(price: ValidNumber, quantity: ValidNumber): ValidNumber {
  return (price * quantity) as ValidNumber;
  // TypeScript ensures price and quantity were validated before use
}

Fix 7: ESLint Rules to Catch NaN Misuse

Add ESLint rules to prevent NaN comparison bugs at lint time:

{
  "rules": {
    "use-isnan": ["error", {
      "enforceForSwitchCase": true,
      "enforceForIndexOf": true
    }]
  }
}

The use-isnan rule catches:

// Flagged by use-isnan:
if (x === NaN) { }           // Error — use Number.isNaN(x)
if (x !== NaN) { }           // Error
switch (x) { case NaN: }     // Error (with enforceForSwitchCase)
arr.indexOf(NaN)             // Error (with enforceForIndexOf) — use arr.includes(NaN)

Still Not Working?

Check for Infinity separately. Number.isNaN(Infinity) is false — Infinity is a valid IEEE 754 value, not NaN. If you need to exclude Infinity too, use Number.isFinite():

Number.isFinite(42);       // true
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity);// false
Number.isFinite(NaN);      // false

// Check for a valid, finite number:
function isValidFiniteNumber(value) {
  return typeof value === "number" && Number.isFinite(value);
}

Check for Object.is() for NaN equality. Object.is() handles NaN correctly and is the basis for SameValue equality used internally by JavaScript:

Object.is(NaN, NaN);  // true  ✓
Object.is(0, -0);     // false ← distinguishes +0 from -0 (unlike ===)
Object.is(1, 1);      // true

// Use in custom equality checks:
function sameValue(a, b) {
  return Object.is(a, b);
}
sameValue(NaN, NaN); // true ✓

For related JavaScript number issues, see Fix: JavaScript TypeError: x is not a function and Fix: JSON.parse Unexpected Token.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles