Null and Undefined: strictNullChecks, Optional Chaining and Nullish Coalescing

Beginner
12 min

Null and Undefined: strictNullChecks, Optional Chaining and Nullish Coalescing

"Cannot read properties of undefined" is the most common runtime error in JavaScript. TypeScript eliminates most of these crashes by treating null and undefined as distinct types that must be handled explicitly. In this lesson you will learn how strictNullChecks works, how narrowing removes null from a type, and how the ?., ?? and ??= operators express "maybe missing" logic in one line.

Two Types, Not One Value

JavaScript has two "nothing" values. TypeScript gives each its own type:

  • undefined: a variable that was declared but not assigned, a missing property, a function with no return.
  • null: an intentional "no value", usually set by your code or returned by DOM and database APIs.

Without strictNullChecks, both are silently members of every type, and let name: string = null compiles. With the flag on (it is part of strict, which every modern project enables), a value can only be null or undefined if its type says so:

typescript
let name: string = null; // Error under strictNullChecks let nickname: string | null = null; // OK: explicitly nullable function find(id: number): User | undefined { /* ... */ }

The compiler then refuses to let you use a possibly-missing value without a check:

typescript
const user = find(7); user.name; // Error: 'user' is possibly 'undefined'

Narrowing Away null and undefined

Any ordinary check narrows the type inside the guarded block:

typescript
const user = find(7); if (user) { // truthiness check console.log(user.name); // user: User } if (user !== undefined) { /* user: User */ } if (user != null) { /* removes both null and undefined */ } const label = user?.name ?? "Guest";

The loose inequality != null is the one place where != is idiomatic in TypeScript: it excludes null and undefined in a single comparison. Early returns also narrow the rest of the function:

typescript
function greet(user: User | null): string { if (!user) return "Hello, stranger"; return `Hello, ${user.name}`; // user is User from here on }

Be careful with truthiness on numbers and strings: if (count) treats 0 and "" as missing. Compare against undefined explicitly when zero or an empty string is a valid value.

Optional Chaining ?.

Optional chaining stops evaluating an expression and returns undefined as soon as it meets null or undefined. It works on properties, element access and calls:

typescript
const city = order.customer?.address?.city; // property chain const first = list?.[0]; // element access const result = callback?.(42); // call only if defined

The type of the whole expression gains | undefined. This replaces long a && a.b && a.b.c chains and compiles down to equivalent checks when your target is older than ES2020.

Nullish Coalescing ?? and ??=

?? returns the right-hand side only when the left is null or undefined. It differs from ||, which also replaces 0, "", false and NaN:

typescript
const port = config.port ?? 3000; // 0 stays 0 const port2 = config.port || 3000; // 0 becomes 3000, probably a bug const retries = options.retries ?? 3; options.timeout ??= 5000; // assign only if currently nullish

| Expression | 0 ?? 10 | 0 \|\| 10 | "" ?? "x" | "" \|\| "x" | null ?? 10 | |---|---|---|---|---|---| | Result | 0 | 10 | "" | "x" | 10 |

Use ?? for defaults on values that may legitimately be falsy; use || only when any falsy value should be replaced.

Where undefined Sneaks In

Some APIs return possibly-missing values that beginners overlook:

  • array.find() and array.pop() return T | undefined.
  • map.get(key) returns V | undefined.
  • Optional properties (prop?: T) read as T | undefined.
  • document.querySelector() returns Element | null.
  • Array index access returns T | undefined when noUncheckedIndexedAccess is enabled.

Handle each with narrowing, ?./??, or a deliberate ! (covered in the previous lesson) when you can prove the value exists.

Common mistakes

  • Using || for numeric defaults and silently turning 0 into the fallback.
  • Chaining ?. everywhere, which hides bugs: only the parts that can genuinely be missing should be optional.
  • Disabling strictNullChecks to make errors go away. The errors are the feature.
Quick Quiz
Question 1 of 3

Under `strictNullChecks`, which declaration compiles?

Key Takeaways

  • strictNullChecks makes null and undefined distinct types that must be declared and handled.
  • Truthiness checks, !== undefined, != null and early returns all narrow a nullable type.
  • ?. short-circuits property, index and call chains to undefined instead of throwing.
  • ?? supplies a default only for null/undefined; || also replaces 0, "" and false.
  • Remember that find, get, querySelector and optional properties all produce possibly-missing values.

Next lesson: Union Types, Intersection Types and Type Narrowing — combine types and let control flow narrow them.

Null and Undefined: strictNullChecks, Optional Chaining and Nullish Coalescing - TypeScript | CodeYourCraft | CodeYourCraft