C.W.K.
Stream
Lesson 02 of 05 · published

`null`, `undefined`, `void`: Three Flavors of Nothing

~11 min · primitives, null, undefined, void

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"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.

The rule of thumb: Reserve 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

My API responses use 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.

Code

Opt-in nullability under strict mode·typescript
// strictNullChecks keeps them separate from other types.

let name: string = 'Pippa';
name = null;                                  // ❌ Type 'null' is not assignable to type 'string'
name = undefined;                             // ❌ Type 'undefined' is not assignable to type 'string'

// To allow them, you opt in explicitly:
let maybeName: string | null = null;          // ✅
let maybeName2: string | undefined = undefined; // ✅
let maybeBoth: string | null | undefined = null; // ✅

// The `?` shorthand on object properties:
interface Profile {
  id: number;
  name: string;
  email?: string;                             // shorthand for: email: string | undefined
}

const p1: Profile = { id: 1, name: 'Pippa' };                  // ✅ email omitted
const p2: Profile = { id: 1, name: 'Pippa', email: 'a@b.c' };  // ✅ email present
Why void exists: callbacks and side effects·typescript
// void vs undefined as return types.

// `void` — return value should be ignored.
function logEvent(name: string): void {
  console.log(name);
  // no return statement needed — and any return value would be ignored
}

// `undefined` — must actually return undefined (or never reach a return).
function findMaybe(id: number): undefined {
  // ❌ if you `return someValue` here, the compiler complains
  return;                                     // ✅ explicit empty return
}

// Callback example — `void` makes the API flexible.
function onClick(handler: (e: Event) => void): void {
  // ...
}

onClick((e) => 42);                           // ✅ — `42` is allowed and ignored
// If the parameter were typed `: undefined` instead, this would fail.

External links

Exercise

Write an interface User with required id: number and name: string, plus optional email?: string. Write a function displayUser(u: User): void that logs the user. Then construct a user with email = null and try to pass it. What does the compiler say, and why? Now change the email field to email: string | null instead of email?: string. What changed?
Hint
Optional ? allows the field to be missing OR undefined. It does NOT allow it to be null. If you actually want null, declare it explicitly in the union. The distinction matters for JSON-shaped data.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.