All topics
Fundamentalsbeginner

NaN and Number Edge Cases

Understanding NaN's self-inequality, floating-point precision issues, and safe ways to check for numeric validity.

NaN means "not a number" and is itself of type number. Its defining property is that it is not equal to itself.

NaN is like a person with no ID who can never prove they're the same person twice, even to themselves. Floating-point imprecision is like trying to represent 1/3 in decimal — you eventually have to round, and that tiny rounding error can snowball.

Key Concepts

1
    NaN === NaN            // false
    [NaN].includes(NaN)    // true  -- includes uses SameValueZero
    [NaN].indexOf(NaN)     // -1    -- indexOf uses ===
2
So testing for it needs a dedicated function.
    Number.isNaN(x)        // true only for actual NaN
    isNaN(x)               // coerces first -- isNaN('abc') is true
    Object.is(x, NaN)      // also correct
3
The global isNaN converts its argument before testing, so it reports true for any value that is not a number at all. Number.isNaN is the one to use.
isNaNtrueNumber.isNaN
4
Where NaN comes from.
    0 / 0
    parseInt('abc')
    Math.sqrt(-1)
    undefined + 1
NaN
5
And it propagates: any arithmetic involving NaN produces NaN, so one bad value silently poisons a whole calculation chain.
NaN
6
Floating point is the other edge.
    0.1 + 0.2 === 0.3      // false
    0.1 + 0.2              // 0.30000000000000004
7
Not a JavaScript flaw — it is IEEE 754 binary floating point, and the same in almost every language. A tenth cannot be represented exactly in binary, just as a third cannot in decimal.
8
Comparing with a tolerance.
    Math.abs(a - b) < Number.EPSILON * Math.max(1, Math.abs(a), Math.abs(b))
9
For money, do not use floats at all. Work in the smallest unit as integers:
10
    1999 cents, not 19.99
11
That is what financial systems do, and it removes the problem rather than tolerating it.
12
Integer safety.
    Number.MAX_SAFE_INTEGER        // 9007199254740991
    Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2  // true
13
Beyond 2^53 integers are no longer exact. A 64-bit id from a backend, sent as a JSON number, can arrive subtly wrong — which is why APIs send large ids as strings, and BigInt exists.
BigInt
14
The other values to recognise.
    Infinity, -Infinity        1/0, and overflow
    -0                         0 === -0 is true; Object.is(0, -0) is false
    Number.isInteger(x)
    Number.isFinite(x)         does not coerce, unlike global isFinite
15
What the interviewer is probing.1. "Why is NaN !== NaN?" Probing: the specification. Stalls: "A bug." Moves up: it is defined as not equal to anything including itself; use Number.isNaN or Object.is to test for it.
16
2. "Why is 0.1 + 0.2 not 0.3?" Probing: floating point. Stalls: "Rounding error." *Moves up:* IEEE-754 doubles cannot represent those decimals exactly; compare within an epsilon or use integers of the smallest unit.
17
3. "What is the largest safe integer?" Probing: precision limits. Stalls: "Infinity." *Moves up:* 2^53 - 1, Number.MAX_SAFE_INTEGER — beyond it integers lose precision, which is why BigInt exists.
18
4. "How does Number.isNaN differ from the global isNaN?" Probing: coercion. Stalls: "They are the same." Moves up: the global coerces first, so isNaN("abc") is true; Number.isNaN only returns true for actual NaN.