Nullability
null, undefined, NaN — three ways to say 'no value'.
Nullability
There are at least three ways to say "no value" in JavaScript: null, undefined, and NaN. They're all different. Python consolidates to None. SQL has its own NULL with three-valued logic. Handling any of them wrong is how you get the "Cannot read properties of null" crash in production.
Analogy
Think of the different ways a form field can be "empty" on a paper questionnaire. A field left completely untouched (undefined) is not the same as one where the user wrote "N/A" (null) on purpose, and neither is the same as one they filled in with gibberish that the scanner choked on (NaN). Treating "untouched" and "declined to answer" as interchangeable is how you accidentally mail a wedding invitation to someone who explicitly told you they aren't coming. SQL takes it one step further: asking "is this field equal to that field?" when either one is blank gets you a shrug, not a yes or no.
null vs undefined
In JavaScript, the two are separate:
undefined= this variable has never been assigned, or this object property doesn't exist. The runtime gives it out by default.null= an explicit "no value". You set it deliberately.
let x; // undefined
const y = null; // null
obj.noSuchKey; // undefined
They coerce similarly but aren't equal under ===:
null == undefined; // true (sloppy ==)
null === undefined; // false
JSON.stringify drops undefined fields silently. null survives. Most APIs therefore use null on the wire and undefined inside JS.
NaN
NaN ("not a number") is what you get from:
0 / 0,Math.sqrt(-1),parseInt("hello")- Operations on
undefined:undefined + 1→NaN
The weirdest rule: NaN !== NaN. It's the only value in JS that is not equal to itself. Use Number.isNaN(x) to test for it:
if (Number.isNaN(x)) { ... }
Don't use the older global isNaN() — it coerces its argument first (isNaN("hello") is true, which is a lie).
Optional / Maybe patterns
Languages with proper sum types express nullability explicitly:
- Rust:
Option<T>=Some(T) | None - Haskell:
Maybe a=Just a | Nothing - TypeScript:
T | undefinedorT | null - Kotlin:
T?
You literally cannot read the value without handling the absence case. The compiler makes you.
?? vs ||
In JavaScript, these behave differently:
0 || 10; // 10 — `||` falls back on ANY falsy (0, "", false, null, undefined)
0 ?? 10; // 0 — `??` only falls back on null/undefined
"" || "n/a"; // "n/a"
"" ?? "n/a"; // ""
false || true; // true
false ?? true; // false
Use ?? whenever 0, empty string, or false is a valid value and only null/undefined should trigger the fallback. This is the right default.
Use || when you really do want to catch all falsy, e.g. const name = user.name || "anonymous" where empty-string should also get the default.
Optional chaining: ?.
?. short-circuits on null/undefined:
user?.profile?.name; // undefined if user is null, no TypeError
arr?.[0]; // undefined if arr is null
fn?.(); // undefined if fn is null
Combines naturally with ??:
const name = user?.profile?.name ?? "guest";
Python has no operator for this. Write user.profile.name if user and user.profile else "guest", or use a helper.
Null-object pattern
Instead of returning null and making every caller check, return a benign "empty" value:
// Bad: caller must null-check every time
getUser(id): User | null
// Better: returns the null-object
getUser(id): User // where the "no user" case returns { id: "", name: "anonymous", ... }
Or use a safe default in the type: tags: string[] instead of tags: string[] | null. An empty list is usually a better signal than null.
strictNullChecks
In TypeScript:
// tsconfig.json
{ "compilerOptions": { "strictNullChecks": true } }
Without it, null and undefined are assignable to every type — you can never tell from the signature whether a function might return null. Always turn it on. The compiler becomes your null-check linter.
SQL NULL — different story
In SQL, NULL is three-valued:
NULL = NULL -- NULL (not true!)
NULL = 1 -- NULL
WHERE x = NULL -- matches nothing, ever
WHERE x IS NULL -- correct way
If you're writing SQL, treat NULL as "unknown". COALESCE(x, fallback) is the SQL equivalent of x ?? fallback.
The rules
- Turn on
strictNullChecks. Let the compiler find the null paths. - Prefer
??over||unless you specifically want falsy-coalescing. - Use
?.for safe property/method/index access on maybe-null things. - Return empty arrays and empty strings instead of null where you can.
- Don't compare to
NaNwith===. UseNumber.isNaN.