There are moments in every developer’s life when JavaScript forces you to question the fundamental nature of reality. Today, we’re diving into one of the most existentially disturbing features of the language: the fact that NaN (literally “Not a Number”) is, according to JavaScript, actually a number.
console.log(typeof NaN); // "number"
Let that sink in for a moment. The thing that is explicitly named “Not a Number” is classified as a number. It’s like JavaScript is gaslighting us at the language level.
This isn’t just a quirky implementation detail – it’s a glimpse into the philosophical chaos that underlies JavaScript’s type system. So grab your favorite debugging beverage and join me on this journey through one of programming’s most beautiful contradictions.
The Paradox in All Its Glory
Let’s start with the basics. In JavaScript, NaN stands for “Not a Number.” It’s what you get when you try to perform mathematical operations that don’t make sense:
console.log(Math.sqrt(-1)); // NaN
console.log(0 / 0); // NaN
console.log(parseInt("hello")); // NaN
console.log("hello" * 2); // NaN
So far, so good. These operations don’t produce valid numbers, so JavaScript gives us NaN. Makes sense, right?
But then comes the plot twist:
console.log(typeof NaN); // "number"
console.log(NaN === NaN); // false (!!!)
console.log(isNaN(NaN)); // true
console.log(Number.isNaN(NaN)); // true
So NaN is simultaneously:
- Not a number (by definition)
- A number (according to
typeof) - Not equal to itself (violating basic mathematical principles)
- Detectable as “not a number” by functions designed to detect it
If this doesn’t make you question the nature of existence, you’re not thinking hard enough.
The IEEE 754 Excuse
Before we completely lose faith in humanity, let’s understand why this madness exists. JavaScript follows the IEEE 754 standard for floating-point arithmetic, which is used by most programming languages.
In IEEE 754, NaN is technically a special value within the number type. It’s not a separate type – it’s a specific bit pattern that represents “this number calculation went wrong.”
// These are all valid IEEE 754 "numbers"
console.log(typeof 42); // "number"
console.log(typeof 3.14); // "number"
console.log(typeof Infinity); // "number"
console.log(typeof -Infinity); // "number"
console.log(typeof NaN); // "number" (because it's a special number value)
So JavaScript isn’t being uniquely weird here – it’s just following a standard that was designed by people who apparently enjoyed philosophical paradoxes.
The Daily Debugging Nightmare
This seemingly academic quirk has real-world consequences that will haunt your debugging sessions:
The typeof Trap
function processNumber(value) {
if (typeof value === "number") {
// This looks safe, right?
return value * 2;
}
throw new Error("Not a number!");
}
console.log(processNumber(5)); // 10
console.log(processNumber("5")); // Error: Not a number!
console.log(processNumber(NaN)); // NaN (no error thrown!)
Your type guard passed, but you’re still dealing with NaN. The function that was supposed to only work with “real” numbers just silently produced garbage.
The Comparison Chaos
const userScore = calculateScore(); // returns NaN due to some edge case
if (userScore === NaN) {
console.log("Invalid score");
}
// This will NEVER execute, even if userScore is NaN!
// Because NaN === NaN is false
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false
console.log(NaN > NaN); // false
console.log(NaN < NaN); // false
console.log(NaN >= NaN); // false
console.log(NaN <= NaN); // false
NaN is the only value in JavaScript that’s not equal to itself. It’s like JavaScript’s way of saying “this value is so broken that it doesn’t even know what it is.”
The Array Method Madness
const numbers = [1, 2, NaN, 4, 5];
// Array methods don't know how to handle NaN
console.log(numbers.indexOf(NaN)); // -1 (not found, because NaN !== NaN)
console.log(numbers.includes(NaN)); // true (but indexOf can't find it!)
// Sorting with NaN is... interesting
console.log([3, NaN, 1, 2].sort()); // [1, 2, 3, NaN] (usually)
The Correct Ways to Handle NaN
Since JavaScript’s built-in behavior is clearly trying to drive us insane, let’s look at the proper ways to deal with NaN:
Detection Methods
// Method 1: Use Number.isNaN() (preferred)
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN(42)); // false
console.log(Number.isNaN("hello")); // false
// Method 2: Use the self-inequality trick
function isActuallyNaN(value) {
return value !== value;
}
console.log(isActuallyNaN(NaN)); // true
console.log(isActuallyNaN(42)); // false
// Method 3: Avoid the global isNaN() function
console.log(isNaN(NaN)); // true
console.log(isNaN("hello")); // true (coerces to NaN first!)
console.log(isNaN(42)); // false
Proper Type Checking
function isValidNumber(value) {
return typeof value === "number" && !Number.isNaN(value) && isFinite(value);
}
console.log(isValidNumber(42)); // true
console.log(isValidNumber(NaN)); // false
console.log(isValidNumber(Infinity)); // false
console.log(isValidNumber("42")); // false
Safe Mathematical Operations
function safeAdd(a, b) {
if (!isValidNumber(a) || !isValidNumber(b)) {
throw new Error("Invalid numbers provided");
}
return a + b;
}
// Or with default values
function safeAddWithDefaults(a, b, defaultValue = 0) {
const numA = isValidNumber(a) ? a : defaultValue;
const numB = isValidNumber(b) ? b : defaultValue;
return numA + numB;
}
The Philosophical Implications
The NaN paradox reveals something deeper about JavaScript’s relationship with truth and identity:
The Problem of Classification
JavaScript’s type system is trying to serve two masters:
- Runtime efficiency: Everything should fit into predefined categories
- Mathematical correctness: Invalid operations should be representable
The result is a compromise that satisfies neither goal completely. NaN is a number that isn’t a number, existing in a quantum superposition of type identity.
The Principle of Least Surprise (Violated)
Most developers expect that:
- If something is “Not a Number,” it shouldn’t be of type “number”
- If two values are identical, they should be equal to each other
- Type checking should actually prevent invalid operations
JavaScript violates all these expectations in the name of IEEE 754 compliance.
Real-World Survival Strategies
1. Always Use Strict Equality and Explicit Checks
// Bad
if (value == someNumber) { /* ... */ }
// Good
if (value === someNumber && !Number.isNaN(value)) { /* ... */ }
2. Validate Early and Often
function processUserInput(input) {
const number = parseFloat(input);
if (Number.isNaN(number)) {
throw new Error(`Invalid number: ${input}`);
}
// Now we know we have a real number
return number * 2;
}
3. Use TypeScript for Better Type Safety
function multiply(a: number, b: number): number {
if (Number.isNaN(a) || Number.isNaN(b)) {
throw new Error("NaN values not allowed");
}
return a * b;
}
4. Create Utility Functions
const NumberUtils = {
isReal: (value) => typeof value === "number" && !Number.isNaN(value) && isFinite(value),
safeOperation: (operation, ...args) => {
if (!args.every(NumberUtils.isReal)) {
throw new Error("Invalid number arguments");
}
return operation(...args);
}
};
// Usage
const result = NumberUtils.safeOperation((a, b) => a + b, 5, 3); // 8
The Bigger Picture: What This Teaches Us
The NaN paradox is a microcosm of JavaScript’s broader design philosophy:
Backwards Compatibility Over Logic
JavaScript prioritizes not breaking existing code over fixing logical inconsistencies. The NaN behavior is here to stay because changing it would break millions of websites.
Standards Compliance Over Developer Experience
Following IEEE 754 was more important than creating an intuitive developer experience. This pattern repeats throughout JavaScript’s design.
The Cost of Flexibility
JavaScript’s dynamic typing and automatic coercion create these edge cases. More rigid type systems avoid these problems by being more restrictive.
The Comedy of Errors
Let’s end with some of the more amusing NaN behaviors:
// NaN propagation is viral
console.log(NaN + 1); // NaN
console.log(NaN * 0); // NaN
console.log(Math.max(NaN, 5)); // NaN
// But sometimes it's not
console.log(Math.min(NaN, 5)); // NaN
console.log(Math.pow(NaN, 0)); // 1 (because x^0 = 1, even for NaN)
// JSON handles NaN... creatively
console.log(JSON.stringify(NaN)); // "null"
// Array methods get confused
console.log([NaN].indexOf(NaN)); // -1
console.log([NaN].includes(NaN)); // true
The Bottom Line
The fact that typeof NaN === "number" perfectly encapsulates JavaScript’s relationship with logic: it’s technically correct (the best kind of correct), but it feels completely wrong to human intuition.
This isn’t a bug – it’s a feature. A feature that teaches us to:
- Never trust
typeofalone for number validation - Always use explicit
NaNchecks - Understand that JavaScript’s type system is more like guidelines than rules
- Question everything, especially when it comes to JavaScript
The next time you encounter NaN in your code, remember: you’re not just dealing with a failed calculation, you’re confronting one of computer science’s greatest philosophical paradoxes. The fact that “Not a Number” is a number is JavaScript’s way of reminding us that in programming, as in life, identity is complicated.
Now go forth and check for NaN properly. Your future self will thank you when you’re not debugging why your “number” type checks are letting invalid values through. 🤯