← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Discriminated Unions

TypeScript 2.0 Intermediate
debt(d7/e5/b3/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no', and the tool listed is TypeScript itself — but TypeScript only catches missing cases if you implement an exhaustiveness check (never guard). Without that pattern, new variants silently fall through switch/if-else chains. The common mistake of forgetting the never check means the gap is invisible until code review or a runtime bug surfaces.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes replacing structs with optional fields across mutually exclusive states with properly discriminated union types each carrying a literal 'kind' or 'status' field. This typically requires updating multiple call sites, type guards, switch statements, and downstream consumers — not a single-line patch but a moderate-to-significant refactor within a component or feature area.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). Discriminated unions apply to web and cli contexts but are scoped to the specific domain model or state machine they represent. Once established, they impose a mild structural tax on maintainers (every new variant must be added to the union and handled in all switch statements), but this is localised to the relevant module and doesn't shape the broader codebase.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception is treating discriminated unions as ordinary union types and missing the power of the discriminant field — specifically, forgetting the never exhaustiveness check so new variants silently fall through. Using boolean discriminants instead of string literals is another common pitfall. These are documented gotchas that developers typically discover after being bitten once, placing this squarely at t5.

About DEBT scoring →

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
    }
}

Added 11 Apr 2026
Views 437
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 1 ping W 1 ping T 1 ping F 0 pings S 4 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 24 Scrapy 7 SEMrush 5 Google 4 Perplexity 3 Ahrefs 3 ChatGPT 2 Unknown AI 1 Claude 1 Meta AI 1 PetalBot 1
crawler 50 crawler_json 2
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


✓ schema.org compliant