Awful Parts in JS: NaN
NaN stands for Not a Number, yet typeof NaN returns 'number', and NaN !== NaN. Here is what makes it confusing and how to handle it correctly.
NaN stands for Not a Number. It is the value returned when a mathematical operation produces a result that cannot be represented as a number. It is one of the more confusing values in JavaScript because its behaviour contradicts what its name implies and because the tools for detecting it have historically been unreliable.
Where NaN Comes From
NaN is produced when a numeric operation fails to produce a valid result:
0 / 0 // NaN
parseInt('hello') // NaN
Math.sqrt(-1) // NaN
undefined + 1 // NaN
These operations do not throw errors. They return NaN and allow execution to continue. The intent is that programs can handle numeric failure without crashing.
The First Problem: typeof NaN Returns “number”
NaN is of type number. This is not a JavaScript-specific quirk. It follows from the IEEE 754 floating-point standard, which JavaScript uses for all numeric values. NaN is defined within the number type as a sentinel value representing an undefined or unrepresentable result.
typeof NaN; // 'number'
The practical consequence is that typeof cannot be used to detect NaN. A check for the type 'number' will return true for both valid numbers and NaN.
The Second Problem: NaN Is Not Equal to Itself
NaN is the only value in JavaScript that is not equal to itself. Any comparison involving NaN returns false, including equality with itself.
NaN === NaN; // false
NaN == NaN; // false
NaN > 1; // false
NaN < 1; // false
NaN !== NaN; // true
This means the standard approach of checking a value by comparing it to a known value does not work with NaN.
The Third Problem: the Global isNaN Function
The global isNaN() function was designed to check for NaN, but it coerces its argument to a number before testing. This produces false positives:
isNaN('hello'); // true — 'hello' coerces to NaN
isNaN(undefined); // true — undefined coerces to NaN
isNaN(null); // false — null coerces to 0
isNaN(''); // false — '' coerces to 0
The function does not answer the question “is this value NaN.” It answers the question “does this value coerce to NaN,” which is a different and less useful question.
The Correct Detection Method: Number.isNaN
ES6 introduced Number.isNaN(), which does not coerce its argument. It returns true only when the value is actually NaN, not when the value merely converts to NaN.
Number.isNaN(NaN); // true
Number.isNaN('hello'); // false
Number.isNaN(undefined); // false
Number.isNaN(42); // false
Number.isNaN is the correct tool for detecting NaN. The global isNaN should be avoided.
The Fourth Problem: NaN Propagates Silently
Any computation that involves NaN produces NaN. If NaN enters a chain of calculations, it contaminates every result downstream without producing an error.
const a = parseInt('bad input'); // NaN
const b = a + 100; // NaN
const c = b * 2; // NaN
console.log(c); // NaN
The original bad input may be far removed from where the NaN eventually surfaces, making the source of the problem difficult to trace.
What to Do Now
Test the difference between isNaN and Number.isNaN directly:
console.log(isNaN('100')); // false — coerces to 100
console.log(isNaN('abc')); // true — coerces to NaN
console.log(isNaN(undefined)); // true — coerces to NaN
console.log(Number.isNaN('abc')); // false — not NaN, it's a string
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(0/0)); // true
Validate numeric inputs at the boundary where they enter your application. Catching NaN early, at the point of input or conversion, prevents it from propagating through calculations where it becomes much harder to find.