Fix: JavaScript NaN === NaN Returns false (NaN Comparison Bugs)
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 quirkOr 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 passOr 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 holdsNaN 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 NaNFix 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 neededWhy this works:
Number.isNaN()only returnstrueif the value is both of typenumberand has the NaN value. Unlike the globalisNaN(), 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 0Fixed 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; // 2Sorting 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 NaNFix 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 !== NaNReplace 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Next.js Middleware Not Running (middleware.ts Not Intercepting Requests)
How to fix Next.js middleware not executing — wrong file location, matcher config errors, middleware not intercepting API routes, and how to debug middleware execution in Next.js 13 and 14.
Fix: TypeScript isolatedModules Errors (const enum, type-only imports)
How to fix TypeScript isolatedModules errors — why const enum fails with Babel and Vite, how to replace const enum, fix re-exported types, and configure isolatedModules correctly for your build tool.
Fix: Vitest Setup Not Working (setupFiles, Mocks, and Global Config Issues)
How to fix Vitest configuration not taking effect — why setupFiles don't run, globals are undefined, mocks don't work, and how to configure Vitest correctly for React, Vue, and Node.js projects.