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')        // NaN

Note 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 NaN

The 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 undefined

No 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 sum

The 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);   // 700

Whether 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      // false

That 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 NaN

The 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)       // true

So 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 not

Convert 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 round

The 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.

Floating-point arithmetic. Not NaN, but the other classic surprise.

0.1 + 0.2           // 0.30000000000000004
0.1 + 0.2 === 0.3   // false

Binary 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.EPSILON

JSON 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.

Frequently Asked Questions

Why does NaN === NaN return false? Because the IEEE floating-point standard defines NaN as unequal to everything including itself — two undefined results are not meaningfully identical. It is the only such value in JavaScript, which is why you must use Number.isNaN to test for it.
What is the difference between isNaN and Number.isNaN? Global isNaN converts its argument first, so isNaN('hello') is true even though a string is not NaN. Number.isNaN checks whether the value itself is NaN with no conversion, which is almost always what you want.
Why is my calculation returning NaN? Some value in it is not numeric — commonly an undefined property, a non-numeric string from an input field, or a missing API field. NaN is contagious, so trace backwards to find the first one rather than the place you noticed it.
Why does Number('') return 0? An empty string converts to zero by specification. It means a blank form field silently becomes a valid zero rather than an error, so check for an empty value before converting if that distinction matters.
How do I check a value is a usable number? Number.isFinite is usually the best single check, since it rejects NaN and Infinity together. Convert with Number() first, then validate, and handle the failure explicitly rather than letting NaN propagate.