NaN is what JavaScript gives you when math goes wrong, like dividing zero by zero. It is still on the number team. It is just a broken number. Math always hands back a number, so when the answer makes no sense, the number it hands back is NaN.
The curse
console.log(typeof NaN); // prints: number
console.log(NaN === NaN); // prints: false
NaN stands for "Not a Number", yet its type is number. And it is not even equal to itself, as you may remember from the double equals lesson.
Where NaN comes from
NaN shows up when a math operation has no sensible numeric answer.
console.log(0 / 0); // prints: NaN
console.log("a" * 2); // prints: NaN
console.log(Math.sqrt(-1)); // prints: NaN
console.log(Number("hello")); // prints: NaN
JavaScript numbers follow the IEEE 754 standard, the same one used by almost every language. That standard reserves a few special values inside the number type, including Infinity, -Infinity and NaN. So NaN is a real value of type number. It means "this calculation broke".
NaN also spreads. Any math with NaN gives NaN, so one bad value early on can turn a whole total into NaN. And the standard says NaN is not equal to anything, itself included, because two broken results should not count as the same answer.
In real code, NaN usually sneaks in through a missing value. Reading a property that does not exist gives undefined, and undefined turns into NaN as soon as you do math with it.
const basket = { apples: 3 };
const fruit = basket.apples + basket.pears;
console.log(fruit); // prints: NaN
console.log(fruit * 10); // prints: NaN
There is no error, so the problem only shows up later, often as the word NaN on a web page. When you see it, work backwards and find the first value that was not a real number.
How to check for NaN
Because NaN === NaN is false, value === NaN is always false too. Use Number.isNaN instead.
console.log(Number.isNaN(NaN)); // prints: true
console.log(isNaN("hello")); // prints: true
console.log(Number.isNaN("hello")); // prints: false
The older global isNaN turns its input into a number first. "hello" becomes NaN on the way in, so isNaN("hello") says true even though you gave it a string. Number.isNaN does no converting. It only says true for the real NaN value.
If you want to know whether some text can be turned into a number, do the conversion yourself and then check, like Number.isNaN(Number(input)). That way it is clear what is happening.
Bonus twist: Object.is
console.log(Object.is(NaN, NaN)); // prints: true
console.log(Object.is(0, -0)); // prints: false
console.log([NaN].includes(NaN)); // prints: true
console.log([NaN].indexOf(NaN)); // prints: -1
Object.is compares by "same value". It treats NaN as equal to NaN, and it tells 0 and -0 apart, which === does not. Array methods differ in the same way. includes can find NaN in an array, while indexOf uses === and never finds it.
Rule of thumb
NaN is a broken number, so check for it with Number.isNaN().
Writing if (score === NaN) to catch bad input. That condition is never true, because NaN is not equal to anything. Use Number.isNaN(score) instead.
Check yourself
The global isNaN converts its input to a number, and "hello" becomes NaN. Number.isNaN("hello") does no converting and returns false.