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

Exhaustive Checks with never

typescript Intermediate

Also Known As

exhaustive switch never check exhaustive union assertNever

TL;DR

Using the 'never' type in a default branch to make TypeScript error at compile time if a union type is not fully handled — ensures every new variant of a type forces a matching handler to be written.

Explanation

TypeScript narrows union types as control flow progresses through if/switch statements. After all known members are handled, the remaining type is 'never' — the empty type that has no values. Assigning that remaining value to a 'never' variable compiles fine if all cases are covered, but errors if any case was missed (because something non-never would be assigned to a never slot). This is the exhaustive check pattern. It catches the most common union-evolution bug: a new member is added to a union type, but the switch statement handling it is not updated — TypeScript immediately errors at every switch that lacks the new case.

Common Misconception

The never check only works with discriminated unions. It works with any union that TypeScript can narrow through control flow — string literals, numeric literals, boolean, null/undefined unions.

Why It Matters

Union types grow over time — new payment methods, new event types, new error codes. Without exhaustive checks, adding a new variant silently falls through to an unhandled default. With exhaustive checks, every switch that handles the union gets a compile error until it is updated. This makes it structurally impossible to forget a case.

Common Mistakes

  • Using 'default: break' without a never check — this silently swallows unhandled cases with no compile-time warning.
  • Not returning from assertNever — it must be typed as 'never' return type; otherwise TypeScript allows it in the default and the exhaustiveness is lost.
  • Forgetting exhaustive checks in if-else chains — switch is the most common place but 'if/else if' on a union also benefits from a trailing else that calls assertNever.
  • Using assertNever in runtime-only code where the union may be extended by third parties — exhaustive checks are appropriate for closed unions you control, not open unions.

Code Examples

✗ Vulnerable
// ❌ No exhaustive check — new union member silently unhandled
type Status = 'active' | 'inactive' | 'suspended';

function getLabel(status: Status): string {
    switch (status) {
        case 'active':   return 'Active';
        case 'inactive': return 'Inactive';
        // 'suspended' added to the union — no error here
        // Returns undefined at runtime
    }
}
✓ Fixed
// ✅ Exhaustive check — new member forces update
function assertNever(x: never): never {
    throw new Error(`Unhandled case: ${JSON.stringify(x)}`);
}

type Status = 'active' | 'inactive' | 'suspended';

function getLabel(status: Status): string {
    switch (status) {
        case 'active':    return 'Active';
        case 'inactive':  return 'Inactive';
        case 'suspended': return 'Suspended';
        default: return assertNever(status);
        // Add 'banned' to Status → compile error here until handled
    }
}

// Inline never check (no helper)
switch (action.type) {
    case 'A': /* ... */ break;
    case 'B': /* ... */ break;
    default:
        const _: never = action; // Error if any case missing
}

Added 23 Mar 2026
Views 26
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 1 ping S 1 ping M 0 pings T 0 pings W 0 pings T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T
No pings yet today
No pings yesterday
Amazonbot 6 Perplexity 4 Google 4 ChatGPT 2 Ahrefs 2 Meta AI 1
crawler 18 crawler_json 1
DEV INTEL Tools & Severity
⚙ Fix effort: Low
⚡ Quick Fix
Create a reusable 'assertNever(x: never): never { throw new Error('Unhandled case: ' + x) }' helper and call it in every switch default. TypeScript errors at compile time; the throw provides a runtime safety net.

✓ schema.org compliant