"Three ways to say 'nothing' — and they all mean slightly different nothings."
Why JavaScript has two nulls
Most languages have one way to say "no value." JavaScript has two — and TypeScript keeps them separate on purpose. null is an explicit absence: code wrote it on purpose, often as a return value from an API ("no result"). undefined is an implicit absence: nobody assigned anything yet (uninitialized variable, missing parameter, missing object property, missing return statement).
At runtime they're distinct values. At the type level they're distinct types. With strictNullChecks: true (which strict: true enables), neither one is silently assignable to other types — you have to handle them, narrow them, or explicitly include them in a union.
The pragma: pick one in your codebase
Both null and undefined are valid TypeScript, but most modern style guides recommend picking one and using it consistently. The TypeScript team itself uses undefined by convention (you'll see it throughout the standard library and the Handbook). Some teams prefer null because it shows up clearly in JSON. Either is defensible. Mixing them carelessly is not — it forces every consumer of your data to handle both shapes of nothing.
cwkPippa's convention: undefined for in-memory absence (optional fields, missing function results), null for database / JSON serialization (because JSON.stringify(undefined) === undefined — undefined doesn't survive JSON, so the wire format uses null).
void — the return type that says "nothing useful"
void is the return type for functions that don't have a useful return value. function logMessage(msg: string): void. The function may technically return undefined, but you're signaling "don't use this return value for anything."
The distinction between void and undefined as return types is subtle but real. A function annotated : void can be passed where any return type is acceptable — the type system understands you're saying "the return is to be ignored." A function annotated : undefined must actually return the value undefined explicitly (or implicitly via no return statement, with strict mode caveats). Use void for callbacks and side-effecting functions; use undefined only when you literally mean the value.
void for function return types. Use undefined for values. Use null only if your codebase style dictates it, or you're crossing a serialization boundary.Optional properties — the ? shorthand
You'll see name?: string in interfaces. This is shorthand for name: string | undefined — the property may be present (and be a string) or absent (and be undefined). With exactOptionalPropertyTypes enabled (a separate strict flag we cover later), the difference between "present and explicitly undefined" vs. "absent entirely" becomes type-system-significant. For now, the shorthand is the common idiom.
Pippa's confession
null at the JSON boundary because TypeScript's undefined doesn't survive serialization. But inside the frontend, the unwrapped value gets normalized — { email: null } from the server becomes { email: undefined } in memory. Then everywhere downstream uses email?: string. The two flavors meet at exactly one place — the wire boundary — and that place is documented.