DEV Community

pickuma
pickuma

Posted on • Originally published at pickuma.com

Null, Undefined, and NaN: What Each Really Means

JavaScript gives you three different ways to say "there is nothing useful here," and they are not interchangeable. null, undefined, and NaN each describe a distinct kind of absence or invalidity, and reading them correctly tells you what actually went wrong.

null vs undefined: who decided there is nothing

The cleanest way to keep these straight is to ask who set the value.

undefined is what the language gives you when nobody assigned anything. A variable you declared but never set is undefined. A property that does not exist on an object is undefined. A function that runs off the end without a return statement evaluates to undefined. An argument you forgot to pass is undefined. In every case, the absence happened automatically — it is the default state of "not filled in."

let x;            // undefined — declared, never assigned
const o = {};
o.missing;        // undefined — no such property
function f() {}
f();              // undefined — no return value
Enter fullscreen mode Exit fullscreen mode

null, by contrast, is something you write on purpose. It is an explicit, deliberate "no value here." When you set user.avatar = null, you are stating that the absence is intentional and meaningful — there genuinely is no avatar, as opposed to "we never looked." This distinction is the whole point of having both: undefined tends to mean "uninitialized or unknown," while null tends to mean "known to be empty."

A famous wart reinforces how separate they are at the type level:

typeof undefined;  // "undefined"
typeof null;       // "object"  ← historical bug, never fixed
Enter fullscreen mode Exit fullscreen mode

typeof null returning "object" is widely regarded as a bug in the original implementation. It was never corrected because too much existing code depends on the current behavior, so it has become a permanent quirk. To reliably check for null, compare directly: value === null.

One more practical note: loose equality blurs the two. null == undefined is true, but null === undefined is false. Most modern code uses strict equality (===) precisely to keep them distinguishable.

NaN: the error value that lives inside numbers

NaN stands for "Not a Number," which is misleading — typeof NaN is actually "number". It is a special value defined by the IEEE 754 floating-point standard that every modern language uses for floating-point math. NaN represents the result of a numeric operation that has no meaningful numeric answer.

You get it from operations like 0 / 0, Math.sqrt(-1), Infinity - Infinity, or trying to coerce something non-numeric: Number("hello") and parseInt("abc") both produce NaN. The idea is that once a calculation goes invalid, the error propagates instead of throwing — any arithmetic involving NaN produces NaN again, so a single bad value can quietly poison a whole chain of math.

The defining property is strange but important: NaN is the only value in the language not equal to itself.

NaN === NaN;   // false
NaN == NaN;    // false
Enter fullscreen mode Exit fullscreen mode

This is required by the floating-point standard — two computations that both failed are not "the same failure." The practical consequence is that you cannot test for NaN with == or ===. Use Number.isNaN(value), which returns true only for an actual NaN.

Because NaN === NaN is false, equality checks silently fail to detect it. Always test with Number.isNaN(x). Avoid the older global isNaN(x), which first coerces its argument — isNaN("hello") returns true even though the string is not NaN. Number.isNaN does no coercion and answers the question you actually asked.

How other languages handle the same problem

The "nothing" problem is universal, and the design choices vary. Python has a single None. Ruby and Lua use nil. Go has a typed nil for pointers, maps, slices, and interfaces. Java and C# have null, and dereferencing it throws the infamous NullPointerException — a class of bug so common its inventor, Tony Hoare, later called null his "billion-dollar mistake."

A different philosophy avoids the magic value entirely. Languages like Rust (Option<T>), Haskell (Maybe), and Swift (optionals) make absence part of the type system: a value is either Some(x) / Just x or explicitly None / Nothing, and the compiler forces you to handle the empty case before you can use the value. That turns "I forgot it might be missing" from a runtime crash into a compile error.

For invalid numeric results, the IEEE 754 NaN is nearly universal across languages that use floating point, so the "not equal to itself" rule you learned in JavaScript holds in Python, C, Java, and most others too.

FAQ


Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.

Top comments (0)