skip to content

NaN, Infinity, and Negative Zero

Numeric edge values behave unlike anything else in the language: NaN is not equal to itself and -0 hides behind ===. Interviewers use them to check whether you reach for the Number.* methods instead of the coercing global functions.

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In JavaScript, why does NaN === NaN evaluate to false, and how do you reliably test whether a value is NaN?

level: juniorimportance: must knowfreq 72%

answer

  1. one value fails self-comparison
  2. an IEEE-754 rule, not a JS quirk
  3. === and indexOf both miss it
  4. includes and Object.is do find it
  5. Number.isNaN does not convert

basics

~10 s

NaN is the only JavaScript value not equal to itself: IEEE-754 makes every comparison involving NaN false, so === and indexOf never find it. Test with Number.isNaN(x), which checks the value without converting it.

solid answer

~40 s

`NaN` means "not a number" — the result of arithmetic with no meaningful numeric answer, like `0/0` or `Math.sqrt(-1)`. IEEE-754 specifies that every equality and relational comparison involving a NaN is false, and JavaScript's strict equality follows that rule, so `NaN === NaN` is false and even `x === x` is false when `x` holds NaN. That makes `===`-based lookups useless for it: `[NaN].indexOf(NaN)` returns `-1`. The reliable test is `Number.isNaN(x)`, which is true only for the actual number NaN and never converts its argument. Two places do find it because they use a different sameness rule: `Array.prototype.includes` and `Object.is(NaN, NaN)` both report true. And `typeof NaN` is `"number"` — NaN is a numeric value, not a separate type or an error.

code

javascript · 10 lines
javascript
const values = [1, NaN, 3];

console.log(NaN === NaN);            // false
console.log(values.indexOf(NaN));    // -1   (indexOf uses ===)
console.log(values.includes(NaN));   // true (includes uses SameValueZero)
console.log(Object.is(NaN, NaN));    // true

console.log(Number.isNaN(values[1])); // true
console.log(Number.isNaN("NaN"));     // false — no conversion
console.log(typeof NaN);              // "number"

go deeper

for a junior

Be able to say plainly that NaN is not equal to itself, that typeof NaN is "number", and that Number.isNaN is the way to test for it. Name one way to produce NaN, such as Number("abc").

for a middle

Explain that the rule comes from IEEE-754's unordered comparison, and that includes, Set/Map keys and Object.is find NaN because they use SameValueZero or SameValue instead of strict equality.

for a senior

Show how NaN propagates silently through arithmetic and where you place validation so a bad field is caught at the boundary rather than surfacing as a NaN total in the UI.

for a principal

Be ready to argue for a house rule: which layer owns numeric validation, whether non-finite values should throw or be dropped, and how that choice keeps NaN from crossing service boundaries.

## What NaN actually is `NaN` is a value of the Number type, produced whenever an arithmetic operation has no meaningful numeric result: `0/0`, `Infinity - Infinity`, `Infinity * 0`, `Math.sqrt(-1)`, `Number("12px")`, `parseInt("abc")`, or any arithmetic touching `undefined` (`undefined + 1`). None of these throw — the operation quietly yields a value meaning "the answer is not a number". Because it is a Number, `typeof NaN` is `"number"`. Reading NaN as an error object or as its own type is the most common junior mistake here. ## Why it is not equal to itself JavaScript inherits its numeric semantics from IEEE-754, the binary floating-point standard. That standard says a NaN compares *unordered* against everything, including another NaN: `==`, `===`, `<`, `>`, `<=` and `>=` all return false when either operand is NaN, and `!=`/`!==` return true. This is not a JavaScript quirk added for convenience — the language just does not override the hardware rule. The motivation is that NaN is not one value but a whole family of "no answer" results. `0/0` and `Math.sqrt(-1)` both produce NaN, but calling them equal would assert that two unknown quantities are the same quantity. IEEE-754 declines to make that claim. The consequence is that NaN is the only JavaScript value for which `x === x` is false — which is itself a (historically popular, now unnecessary) way to detect it: ```js const x = 0 / 0; console.log(x === x); // false ``` ## What breaks because of it Anything built on strict equality misses NaN: ```js const values = [1, NaN, 3]; values.indexOf(NaN); // -1 — indexOf uses === values.lastIndexOf(NaN); // -1 values.filter(v => v === NaN); // [] — never matches ``` A switch statement on a NaN scrutinee falls through to `default`, because `switch` also uses strict equality. Equality-based cache hits and "has this changed?" guards silently report "different" every time when the value is NaN. ## The three sameness rules, and which one finds NaN JavaScript has several notions of sameness, and they disagree precisely on NaN: - **Strict equality (`===`)** — NaN is not equal to NaN. Used by `===`, `indexOf`, `switch`. - **SameValueZero** — NaN *is* the same as NaN; `+0` and `-0` are treated as the same. Used by `Array.prototype.includes`, `Map` keys and `Set` members. - **SameValue** — like SameValueZero, but also distinguishes `+0` from `-0`. Exposed as `Object.is`. So: ```js [NaN].includes(NaN); // true new Set([NaN, NaN]).size; // 1 — de-duplication works Object.is(NaN, NaN); // true ``` That is why `Set`-based de-duplication of an array containing NaN behaves the way you would want, while `indexOf`-based de-duplication does not. ## Detecting NaN The direct test is `Number.isNaN(value)`, added in ES2015. It returns `true` only when the argument is the Number value NaN, and it performs **no conversion**: ```js Number.isNaN(NaN); // true Number.isNaN(0 / 0); // true Number.isNaN("NaN"); // false — a string is not the number NaN Number.isNaN(undefined); // false ``` The older global `isNaN` is a different function with a different contract: it converts its argument to a Number first, so `isNaN("abc")` is `true` and `isNaN("")` is `false`. Treat the two as unrelated functions that unfortunately share a name. When the real question is "is this a usable finite number?", `Number.isFinite(value)` is usually the better guard, because it rejects NaN, `Infinity` and `-Infinity`, and non-numbers, in one check. ## Where it bites in practice NaN is contagious: every arithmetic operation involving it produces NaN, so a single bad input turns a whole downstream computation into NaN with no indication of which input was at fault. A `reduce` that sums amounts returns NaN if one row's amount was `undefined` or an unparsable string. The fix is not to test the final total but to validate each value at the boundary where it enters the computation, with `Number.isFinite`. A second everyday consequence: NaN is **falsy**, so `NaN || 0` yields `0`, while `NaN ?? 0` yields NaN — nullish coalescing only replaces `null` and `undefined`, and NaN is neither.

  • If NaN is not equal to itself, how does putting NaN into a Set still de-duplicate correctly?
    `Set` and `Map` compare keys with SameValueZero rather than strict equality, and SameValueZero treats NaN as the same value as NaN. So `new Set([NaN, NaN])` has size 1 and `set.has(NaN)` is true. `Array.prototype.includes` uses the same rule, which is why it finds NaN where `indexOf` returns -1.
  • What does the expression x !== x tell you, and would you write it in production code?
    It is true only when `x` is NaN, since NaN is the sole value not equal to itself — it was the standard pre-ES2015 NaN test. In modern code I would write `Number.isNaN(x)` instead: it says exactly what it means, survives review, and does not look like a typo. `x !== x` is worth recognising when reading older libraries.
  • Why do relational comparisons like NaN < 5 also return false?
    IEEE-754 defines NaN as unordered with respect to every value, so `<`, `>`, `<=` and `>=` are all false when either operand is NaN. This matters for sorting and clamping: a comparator that returns `a - b` produces NaN for a NaN element, and the resulting order is unreliable. Filter non-finite values out before comparing.

saying these in an interview costs you the question

  • Says typeof NaN is "NaN" or that NaN is its own type
  • Claims NaN !== NaN is a JavaScript bug rather than IEEE-754
  • Uses indexOf or === to look for NaN in an array
  • Thinks NaN throws an error instead of propagating silently
  • Assumes Number.isNaN("abc") is true — it does not convert

context

open as a page

How do JavaScript's global isNaN() and isFinite() differ from Number.isNaN() and Number.isFinite(), and which would you use to validate a numeric field?

level: middleimportance: must knowfreq 58%

basics

~10 s

The global isNaN and isFinite convert their argument to a number first, so isNaN("") is false and isFinite("100") is true. The ES2015 Number.isNaN and Number.isFinite never convert: any non-number returns false.

open as a page

In JavaScript, which operations produce Infinity and which produce NaN? Explain the rules with examples.

level: middleimportance: should knowfreq 42%

basics

~10 s

A nonzero number divided by zero, or arithmetic that overflows the double range, gives a signed Infinity. Genuinely indeterminate forms give NaN: 0/0, Infinity - Infinity, Infinity * 0, Infinity / Infinity. Neither throws.

open as a page

How does a value in JavaScript end up as -0, and how do you detect it given that -0 === 0 is true?

level: middleimportance: should knowfreq 38%

basics

~20 s

IEEE-754 keeps a sign bit even when the magnitude is zero, so 0 * -5, Math.round(-0.4) and underflow all give -0. Strict equality cannot see it; use Object.is(x, -0) or check that 1 / x is -Infinity.

open as a page

A JavaScript dashboard computes a total from API data and intermittently renders it as NaN. How do you find where the NaN came from, and how do you stop this class of bug?

level: seniorimportance: should knowfreq 36%

basics

~20 s

NaN is contagious and carries no provenance: one bad field poisons every downstream result. Bisect by testing each input with Number.isFinite instead of inspecting the total, then validate and reject non-finite values at the boundary where data enters.

open as a page