kind Discriminated Unions
Last updated: 2026-10-22
kind Discriminated Unions
A discriminated union (also called a tagged union) ties a group of types together with a common discriminant field (status, kind...) whose value is a distinct literal per member. When you switch on the discriminant, TypeScript narrows to the matching member — the most practical union pattern in business modeling.
| Group | Narrowing & Assertions |
|---|---|
| Kind | Type |
| Strict mode | Not required |
ℹ️ Info The editor transpiles TypeScript to JavaScript and runs it (no type checking); see each example's Output for type-check results.
📝 Syntax
type Result<T> =
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
function handle<T>(res: Result<T>) {
switch (res.status) {
case "loading": /* res: no data */ break;
case "success": res.data; break; // narrowed to the success member
case "error": res.message; break;
}
}
⚙️ Parameters
| discriminant | The discriminant: every member must declare it, with a distinct literal type per member (e.g. "loading" / "success" / "error") |
|---|
Returns:None — discriminated unions are a type-level modeling device; narrowing is triggered by switch/if comparisons on the discriminant and happens at compile time.
▶ Example
Edit the code below and click Try it to see the transpiled result:
Output:
loading…
got 3 items
error: timeout
(Runtime output after transpilation; res.data type-checks in the success branch thanks to discriminant narrowing — guaranteed statically by tsc, at runtime there are only values)
💡 Notes
ℹ️ Info The discriminant name is up to you — type, kind, status, state, tag are all common; what matters is a distinct literal value per member so TS can identify it uniquely.
⚠️ Note The discriminant must be a required field of literal type. Writing string instead of a literal like "success", or making it optional, destroys the discriminating power and narrowing stops working.
💡 Tip Discriminated unions pair perfectly with exhaustiveness checking: once every discriminant value is switched, adding a member makes the compiler flag every missed branch (use assertNever — see the exhaustive-checks entry).
🏷️ Related Narrowing & Assertions
💡 Related cases
More cases coming soon.
📚 Related tutorials
❓ FAQ
Q How is a discriminated union different from a plain union?
A A plain union narrows only via generic checks like typeof/in; a discriminated union adds a literal discriminant so a single switch lands you on exactly one member, and it enables exhaustiveness checking. Design a discriminant whenever you can.
Q Can the discriminant be nested inside an object?
A Yes — TS narrows on nested discriminants (and even array elements), e.g. res.meta.state. But deep nesting hurts readability; the convention is a top-level discriminant.
Q Can a numeric enum serve as the discriminant?
A Yes. Enum members (numeric included) are literal types, so distinct enum values per member form a valid discriminated union; string enums read better than numeric ones when debugging.