4. Unions, narrowing & discriminated unions
TypeScript · 44 pages source format
Union types model "A or B". Narrowing refines unions with typeof, instanceof, in, and truthiness checks. Discriminated unions add a shared literal field for exhaustive switch — the pattern for API states and UI machines.
What you'll learn
- Union types
- Type narrowing
- Discriminated unions
- Exhaustiveness checking
- never type
Basic unions
type Id = string | number. Functions accepting unions must handle all cases or narrow first.
Do this now
Write formatId(id: string | number): string handling both.
Narrowing techniques
typeof for primitives. instanceof for classes. "field" in obj for object shapes. Truthiness for null/undefined.
function printValue(val: string | number | boolean) {
if (typeof val === "string") console.log(val.toUpperCase())
else if (typeof val === "number") console.log(val.toFixed(2))
else console.log(val ? "yes" : "no")
}Do this now
Write printValue(val: string | number | boolean) using typeof narrowing.
Discriminated unions
Shared literal field (kind/status) enables exhaustive switch. Compiler warns on missing cases with never.
type FetchState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string }Do this now
Model fetch state: idle | loading | success | error with discriminated union. Render function with switch.
Exhaustiveness check
default: const _exhaustive: never = state catches unhandled cases at compile time.
Do this now
Add a new status to union. See compiler error until switch updated.
Result type pattern
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }. Safer than throw for expected failures.
Do this now
Wrap getPost in Result<Post> instead of throwing. Caller narrows on ok.