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:

TS strict.ts
let nickname: string = "Jo";
nickname = null;

let maybeName: string | null = "Jo";
maybeName = null;
tsc output
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:

TS must-check.ts
function findUser(id: number): { name: string } | undefined {
  return id === 1 ? { name: "Ana" } : undefined;
}

const user = findUser(2);
console.log(user.name);
tsc output
error TS18048: 'user' is possibly 'undefined'.

Narrowing with a check

TS check.ts
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");
}
Output
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:

TS optional-chaining.ts
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?.());
Output
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:

TS nullish.ts
function pageSize(setting?: number) {
  return setting ?? 20;
}

console.log(pageSize());
console.log(pageSize(50));
console.log(pageSize(0));
console.log(0 || 20);
Output
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":

TS non-null.ts
const settings = new Map<string, string>([["lang", "en"]]);

const lang = settings.get("lang")!;
console.log(lang.toUpperCase());
Output
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:

TS optional-props.ts
function greet(name?: string) {
  // name: string | undefined
  return `Hello, ${name ?? "stranger"}!`;
}

console.log(greet());
console.log(greet("Ida"));
Output
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.

Try it yourself

Try it yourself
Console output