null, undefined & Optional Chaining
"Cannot read properties of undefined" is the most common JavaScript crash. With strict null checks on, TypeScript tracks exactly where null and undefined might appear and makes you handle them — and modern JavaScript operators make handling them painless.
Strict null checks
With "strict": true (which includes strictNullChecks), null and undefined are their own types. A plain string can't hold them:
let nickname: string = "Jo"; nickname = null; let maybeName: string | null = "Jo"; maybeName = null;
error TS2322: Type 'null' is not assignable to type 'string'.
If a value really can be missing, say so in the type with a union like string | null. Then TypeScript makes sure you check before using it:
function findUser(id: number): { name: string } | undefined { return id === 1 ? { name: "Ana" } : undefined; } const user = findUser(2); console.log(user.name);
error TS18048: 'user' is possibly 'undefined'.
Narrowing with a check
function findUser(id: number): { name: string } | undefined { return id === 1 ? { name: "Ana" } : undefined; } const user = findUser(1); if (user) { console.log(user.name); } else { console.log("no such user"); }
Ana
Inside the if, TypeScript knows user isn't undefined. Checks like user !== undefined, user != null (which excludes both null and undefined) or an early return work the same way.
Optional chaining: ?.
When you just want to read through a value that might be missing, ?. stops and returns undefined instead of crashing:
type Profile = { address?: { city?: string }; getAge?: () => number }; const a: Profile = { address: { city: "Oslo" } }; const b: Profile = {}; console.log(a.address?.city); console.log(b.address?.city); console.log(b.getAge?.());
Oslo undefined undefined
a?.b reads a property, a?.[key] reads by index, and fn?.() calls a function only if it exists. The result's type includes undefined, so TypeScript will still make you deal with it later.
Nullish coalescing: ??
?? supplies a fallback when the left side is null or undefined:
function pageSize(setting?: number) { return setting ?? 20; } console.log(pageSize()); console.log(pageSize(50)); console.log(pageSize(0)); console.log(0 || 20);
20 50 0 20
Notice the difference from ||: || treats every falsy value — 0, "", false — as missing, so a deliberate setting of 0 would be thrown away. ?? only falls back for genuinely absent values. There's also an assignment form, x ??= value, which sets x only if it's currently nullish.
The non-null assertion: !
A ! after an expression tells TypeScript "trust me, this isn't null or undefined":
const settings = new Map<string, string>([["lang", "en"]]); const lang = settings.get("lang")!; console.log(lang.toUpperCase());
EN
Map.get returns string | undefined, because the key might not exist. The ! removes undefined from the type. It's erased at compile time and performs no check — if you're wrong, you get exactly the runtime crash TypeScript was trying to prevent. Prefer a real check or ?? unless you're certain.
Optional properties and parameters
A ? on a property or parameter adds undefined to its type automatically:
function greet(name?: string) { // name: string | undefined return `Hello, ${name ?? "stranger"}!`; } console.log(greet()); console.log(greet("Ida"));
Hello, stranger! Hello, Ida!
null or undefined?
JavaScript has two "nothing" values, and APIs use them inconsistently: the DOM returns null (document.querySelector), while missing properties and parameters are undefined. Many TypeScript codebases settle on undefined for their own code and only handle null where an outside API produces it. Whatever you choose, ?. and ?? treat the two identically.