Optional Fields and Nullability
Model values that might be missing, and let the compiler force you to handle that case.
What you'll learn
- Mark a field optional and explain the type it actually receives
- Handle a possibly-undefined value before using it
- Use optional chaining and nullish coalescing correctly
Prerequisites
Explanation
"Cannot read properties of undefined" is the most common runtime error in JavaScript. Under strict mode, TypeScript exists largely to make it impossible.
Optional fields
A ? after a field name means the field may be absent:
interface Profile {
name: string;
nickname?: string;
}
nickname now has type string | undefined. That is the important part: optional is not a separate concept, it is a union with undefined. Which means everything you learned about unions applies — you must narrow before you use it.
function greet(p: Profile): string {
return "Hi " + p.nickname.toUpperCase(); // rejected: possibly undefined
}
The compiler is describing a real crash for every profile without a nickname.
Handling it
A plain check narrows, exactly like last lesson:
if (p.nickname) {
return "Hi " + p.nickname.toUpperCase(); // now string
}
return "Hi " + p.name;
Two operators make this shorter.
Optional chaining ?. stops and returns undefined instead of throwing:
const upper = p.nickname?.toUpperCase(); // string | undefined
Nullish coalescing ?? supplies a fallback when the left side is null or undefined:
const display = p.nickname ?? p.name; // string
Use ?? rather than || when the value could legitimately be 0 or "". || falls back on any falsy value, so count || 10 gives 10 when count is 0 — usually a bug. count ?? 10 gives 0, because 0 is not nullish.
null versus undefined
Both exist and they are different types. A workable convention: undefined means "not provided", null means "explicitly empty". If you need to accept either, say so: string | null | undefined.
Why this is worth the friction
Every one of these errors is a crash you would otherwise ship. The compiler is not adding work — it is moving work from your users' browsers to your editor.
Example
An optional field handled three ways: an explicit check, optional chaining, and a fallback.
interface Profile {
name: string;
nickname?: string;
age?: number;
}
const withNick: Profile = { name: "Adaeze", nickname: "Ada" };
const withoutNick: Profile = { name: "Grace" };
function display(p: Profile): string {
return p.nickname ?? p.name;
}
function shout(p: Profile): string {
return p.nickname?.toUpperCase() ?? "NO NICKNAME";
}
console.log(display(withNick), display(withoutNick));
console.log(shout(withNick), shout(withoutNick));
// ?? only falls back on null/undefined, so a real 0 survives:
const age = withoutNick.age ?? 0;
console.log(age);Try it yourself
Remove the ?? fallback in display and Run. The compiler explains exactly what could be undefined.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Define `interface Settings { theme: string; fontSize?: number }`. Write `resolveFontSize(s: Settings): number` returning the fontSize when present, otherwise 16. A fontSize of 0 must be respected, not replaced.
Checks: resolveFontSize is defined · Returns the provided fontSize · Defaults to 16 when absent · plus 1 hidden check
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Define `interface User { name: string; email?: string }`. Write `contactLine(u: User): string` returning `"Ada <ada@example.com>"` when an email exists, and `"Ada (no email)"` when it does not.
Checks: contactLine is defined · Formats a user with an email · Formats a user without an email
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Using `||` for defaults where 0 or an empty string is a legitimate value. `??` only falls back on null and undefined.
- Thinking `?.` makes an error disappear. It changes the result type to include `undefined`, which you still have to handle.
- Marking a field optional to silence an error when the field is genuinely always present — which pushes the check onto every consumer forever.
Knowledge check
Takeaway
Optional means `| undefined`; the compiler will not let you use the value until you have dealt with that.
Summary
`field?: T` produces `T | undefined`. Narrow it with a check, reach into it safely with `?.`, and supply a fallback with `??` — not `||`, which also replaces legitimate falsy values like 0.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.