What you'll learn
Quick Answer
NaN means Not a Number and appears whenever an arithmetic operation cannot produce a meaningful numeric result — usually because a value was a non-numeric string, undefined, or a missing property. It is contagious, since any arithmetic involving NaN produces NaN. It is also the only value in JavaScript not equal to itself, so check for it with Number.isNaN rather than a comparison, and validate input at the point it enters your code.
What Actually Creates NaN
NaN is a numeric value representing the result of an impossible calculation. Confusingly, typeof NaN is "number" — it is a number that is not a number.
Number('hello') // NaN — cannot parse
undefined + 1 // NaN — undefined has no numeric value
0 / 0 // NaN — mathematically undefined
Math.sqrt(-1) // NaN — no real result
parseInt('abc') // NaNNote which things do not produce NaN, because the asymmetry causes real bugs:
null + 1 // 1 — null converts to 0
true + 1 // 2 — true converts to 1
'5' * 2 // 10 — numeric string converts fine
'5' + 2 // '52' — but + concatenates instead
[] + 1 // '1' — empty array becomes ''
1 / 0 // Infinity, not NaNThe most common real-world source is a property that does not exist:
const user = { age: 25 };
const total = user.years + 5; // NaN — user.years is undefinedNo error is thrown. You just get NaN, and it spreads.
NaN Is Contagious
Any arithmetic involving NaN produces NaN. So one bad value at the start turns an entire pipeline of correct code into nonsense.
const prices = [100, 200, 'N/A', 400];
const total = prices.reduce((sum, p) => sum + Number(p), 0);
// NaN — one unparseable entry poisons the whole sumThe debugging consequence matters: the place you notice NaN is not where it originated. A NaN displayed on screen may have been introduced five functions earlier.
So the task is finding the first NaN. Work backwards from the symptom, logging inputs rather than outputs:
const total = prices.reduce((sum, p) => {
const n = Number(p);
if (Number.isNaN(n)) console.warn('bad value:', p); // names the culprit
return sum + n;
}, 0);A more robust version filters or defaults rather than propagating:
const total = prices
.map(Number)
.filter(n => !Number.isNaN(n))
.reduce((a, b) => a + b, 0); // 700Whether skipping bad data or failing loudly is correct depends on your situation — but silently returning NaN is never the right answer.
Checking for NaN Correctly
NaN is the only value in JavaScript that is not equal to itself.
NaN === NaN // false
NaN == NaN // falseThat is not a quirk of the language's design so much as a rule inherited from the IEEE floating-point standard: two undefined results are not meaningfully the same. But it means the obvious check does not work.
Use Number.isNaN:
Number.isNaN(value) // true only if value is actually NaNThe older global isNaN is a trap, because it converts its argument first:
isNaN('hello') // true — coerced to NaN, then tested
isNaN(undefined) // true — same
isNaN('123') // false
Number.isNaN('hello') // false — the string is not NaN itself
Number.isNaN(NaN) // trueSo global isNaN answers "would this become NaN if converted?", while Number.isNaN answers "is this NaN?". The second is almost always what you want.
Number.isFinite is often even better, since it rejects NaN and Infinity in one check:
Number.isFinite(total) ? total : 0
Converting Input Safely
Most NaN bugs come from user input, which is always a string.
const qty = document.querySelector('#qty').value; // '5' — a string
qty * 2; // 10 — works by coercion
qty + 2; // '52' — does notConvert explicitly and validate immediately:
const qty = Number(input.value);
if (!Number.isFinite(qty)) {
showError('Please enter a number');
return;
}Number() and parseInt() differ in ways that matter:
Number('12abc') // NaN — strict, all or nothing
parseInt('12abc') // 12 — reads as far as it can
Number('') // 0 — empty string becomes zero!
parseInt('') // NaN
Number(' 12 ') // 12 — whitespace trimmed
parseInt('0.9') // 0 — truncates, does not roundThe Number('') === 0 case is a genuine trap: an empty form field silently becomes zero rather than an error, so a blank quantity is treated as a valid order of none.
Always pass a radix to parseInt when parsing user input: parseInt(value, 10). Without it, some engines historically treated a leading zero as octal.
Related Number Traps Worth Knowing
Floating-point arithmetic. Not NaN, but the other classic surprise.
0.1 + 0.2 // 0.30000000000000004
0.1 + 0.2 === 0.3 // falseBinary floating point cannot represent 0.1 exactly, so tiny errors accumulate. Compare with a tolerance, or work in the smallest unit — store paise as integers rather than rupees as decimals, which is what payment systems do.
Math.abs(a - b) < Number.EPSILONJSON drops NaN. It is not valid JSON, so it silently becomes null:
JSON.stringify({ x: NaN }) // '{"x":null}'That is how a NaN in the browser becomes a null in the database, appearing to be a different bug entirely.
Sorting numbers. Not NaN-related but the same family of surprise — the default sort compares as strings:
[10, 9, 100].sort() // [10, 100, 9]
[10, 9, 100].sort((a, b) => a - b) // [9, 10, 100]And if the array contains a NaN, the comparator returns NaN, leaving the order effectively undefined — which is why filtering invalid numbers before sorting matters.
