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.
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).
They allow TypeScript to narrow complex object shapes instantly in switch statements or if blocks with 100% type safety.
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!
}
}
[ NetworkState Union ]
├── status: "idle"
├── status: "loading"
├── status: "success" (data: string[]) <-- Narrowed via case "success"!
└── status: "error" (error: Error)
kind on one member and type on another prevents TypeScript from identifying the discriminator tag. Keep the discriminator property name identical across all members!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!
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.
`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.
`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.