Discriminated Unions
Also Known As
tagged unions
sum types
variant types
algebraic data types ADT
TL;DR
A union of types that each carry a shared literal field (the discriminant) allowing TypeScript to narrow exhaustively — the canonical way to model mutually exclusive states.
Explanation
Each member of the union has a shared property with a unique literal type (kind, type, status, tag). TypeScript uses the discriminant to narrow inside switch/if blocks with no runtime overhead beyond the field check. Pattern replaces: class hierarchies, boolean flags, optional fields that are only set in certain states. Exhaustiveness: add a default: const x: never = value branch to get a compile error when a new union member is added but not handled. Common in Redux actions, API response variants, result types (Ok | Err), FSM states.
Diagram
stateDiagram-v2
[*] --> loading
loading --> success : data fetched
loading --> error : request failed
success --> [*]
error --> loading : retry
Watch Out
⚠ The discriminant field must be a literal type (string, number, boolean literal) — if it's a broad string the narrowing won't work.
Common Misconception
✗ Discriminated unions are just regular union types — the discriminant field is what makes them powerful: TypeScript can narrow exhaustively and catch missing cases at compile time.
Why It Matters
Without discriminated unions, modelling mutually exclusive states requires optional fields that can be in impossible combinations — discriminated unions make impossible states unrepresentable.
Common Mistakes
- Using a boolean discriminant — booleans only give two variants; use a string literal for extensibility.
- Forgetting the never exhaustiveness check — new variants added later silently fall through.
- Putting shared fields on the union level instead of each member — breaks narrowing.
- Using type assertions instead of discriminant checks — bypasses the safety.
Avoid When
- When variants share most fields and only differ in one optional aspect — a simple optional field is cleaner.
- When the number of variants changes very frequently and exhaustiveness checking becomes a maintenance burden.
When To Use
- Modelling FSM states, async request lifecycle (loading/success/error), or Redux action types.
- Any time you have mutually exclusive variants that carry different data.
Code Examples
💡 Note
Replaces an ambiguous optional-field shape with a discriminated union where each state carries only the fields that exist in that state — impossible combinations are removed entirely.
✗ Vulnerable
// Impossible state possible: what if both error and data are set?
type ApiResponse = {
loading: boolean;
data?: User;
error?: string;
};
if (response.data) {
// error might also be set — ambiguous
✓ Fixed
// Discriminated union — impossible states are impossible
type ApiResponse =
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; message: string };
function render(r: ApiResponse) {
switch (r.status) {
case 'loading': return <Spinner />;
case 'success': return <UserCard user={r.data} />;
case 'error': return <ErrorMsg text={r.message} />;
default:
const _: never = r; // compile error if a variant is unhandled
}
}
Tags
🤝 Adopt this term
£79/year · your link shown here
Added
11 Apr 2026
Views
335
🤖 AI Guestbook educational data only
|
|
Last 30 days
Agents 0
No pings yet today
No pings yesterday
Amazonbot 20
Perplexity 3
Google 2
ChatGPT 2
SEMrush 2
Unknown AI 1
Ahrefs 1
Also referenced
How they use it
crawler 30
crawler_json 1
Related categories
⚡
DEV INTEL
Tools & Severity
🟡 Medium
⚙ Fix effort: Medium
⚡ Quick Fix
Replace structs with optional fields representing mutually exclusive states with a union of types each having a literal 'kind' or 'status' field.
📦 Applies To
typescript 2.0
web
cli
🔗 Prerequisites
🔍 Detection Hints
\?: .*\|.*;
Auto-detectable:
✗ No
typescript
⚠ Related Problems
🤖 AI Agent
Confidence: Low
False Positives: High
✗ Manual fix
Fix: High
Context: File
Tests: Update