Discriminated Unions & Pattern Matching
Quick Overview
Give every member of a union the same literal tag field and a check on that tag narrows the whole object. That is what lets `state.error` be a type error unless the tag says the state actually has one.
Deep Dive
1. Tagged State Machines
A Discriminated Union (also called a Tagged Union or Algebraic Data Type) is a union of object shapes that share a single common literal property (the "discriminator" tag).
2. Why Discriminated Unions Rule
They allow TypeScript to narrow complex object shapes instantly in switch statements or if blocks with 100% type safety.
3. The Pattern
Define a common literal property (e.g. kind or status) across all union members:
type NetworkState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; error: Error };
function renderUI(state: NetworkState): string {
// Narrowing occurs automatically based on the 'status' tag!
switch (state.status) {
case "idle":
return "Ready to load.";
case "loading":
return "Loading data...";
case "success":
return `Loaded ${state.data.length} items.`; // Safe to access state.data!
case "error":
return `Error: ${state.error.message}`; // Safe to access state.error!
}
}
4. Visual Discriminated Narrowing
[ NetworkState Union ]
├── status: "idle"
├── status: "loading"
├── status: "success" (data: string[]) <-- Narrowed via case "success"!
└── status: "error" (error: Error)
5. Common Mistakes
- Mismatched Discriminator Names: Using
kindon one member andtypeon another prevents TypeScript from identifying the discriminator tag. Keep the discriminator property name identical across all members!
6. Next Steps
Use discriminated unions for Redux actions, API state machines, and AST nodes. Next, let's explore Built-in Utility Types: Partial, Required, Readonly, Pick, and Omit!
Next Up in TypeScript & Type System Mechanics
TypeScript Primitives & Explicit Type Annotations
TypeScript erases entirely at build time, so a `string` annotation produces no runtime check whatsoever. A value arriving from an API is whatever the server actually sent, regardless of what the annotation claims.
Type Inference & Literal Types (const vs. let)
`let x = "GET"` infers `string` because the binding can change; `const x = "GET"` infers the literal `"GET"`. That widening rule is why object properties need `as const` to stay narrow.
Arrays, Tuples & Readonly Modifiers
`string[]` is any number of strings; `[string, number]` is exactly two, in that order. `readonly` removes the mutating methods at compile time, so `push` becomes a type error rather than a runtime surprise.
