Optional Chaining, Nullish Coalescing & Non-null
The three operators for working with maybe-missing values β and the sharp edge on the last one.
Three operators dominate null-handling in modern TypeScript. The first two are safe and delightful; the third is a promise you make to the compiler.
Optional chaining ?.
a?.b short-circuits to undefined the moment a is null or undefined, instead of throwing. It works on properties, calls (fn?.()), and indexes (arr?.[0]).
Nullish coalescing ??
a ?? b returns b only when a is null or undefined β unlike ||, it lets 0, "", and false through as real values.
const port = config.port ?? 3000; // 0 would be kept; with || it would not
const name = user?.profile?.name ?? "Guest";Non-null assertion !
value! tells the compiler "trust me, this is not null here." It removes null/undefined from the type but does nothing at runtime. If you are wrong, you get the very crash TypeScript was trying to prevent.
Example
When to use it
- A navigation menu uses optional chaining to safely access a nested user.profile.avatar URL without nested null checks.
- A configuration loader uses nullish coalescing to fall back to a default port when the env variable is undefined.
- A developer uses the non-null assertion on a DOM element that is guaranteed to exist by the page template.
More examples
Optional chaining on nested object
Optional chaining (?.) short-circuits to undefined if profile is missing, and ?? provides a fallback image path.
interface User {
profile?: {
avatar?: string;
};
}
function getAvatar(user: User): string {
return user.profile?.avatar ?? '/default.png';
}Nullish coalescing vs OR
Contrasts ?? (nullish only) with || (all falsy) to show why ?? is safer when 0 or empty string are valid values.
const port = process.env.PORT ?? 3000; // fallback only on null/undefined
const label = process.env.LABEL || 'app'; // fallback on any falsy, including ''
console.log('port:', port);
console.log('label:', label);Non-null assertion vs guard
Shows the ! non-null assertion alongside a null guard, highlighting that the guard is safer but ! is concise when presence is guaranteed.
// Non-null assertion β use sparingly
const btn = document.getElementById('submit')!;
btn.addEventListener('click', () => console.log('clicked'));
// Safer: explicit check
const btn2 = document.getElementById('submit');
if (btn2) {
btn2.addEventListener('click', () => console.log('clicked'));
}
Discussion