intermediate22 min

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.

Loading editor…

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.

Loading editor…

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.

Loading editor…

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

Knowledge check

1. What is the type of `nickname` in `interface P { nickname?: string }`?
2. Given `const n: number | undefined = 0`, what does `n ?? 10` produce?
3. What does `user.profile?.city` evaluate to when `profile` is undefined?

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.

Next: Typing Functions