Interactive Lab

Sound Type Narrowing & Exhaustive Unions in TS6

Master discriminated unions, the satisfies operator, and compile-time exhaustiveness guarantees for bulletproof application state.

unions-and-satisfies.ts
// 1. Discriminated Union Pattern
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading'; progress: number }
  | { status: 'success'; data: T; timestamp: number }
  | { status: 'error'; code: number; message: string };

// 2. Exhaustive Matcher Pattern (assertNever)
function getStatusBadge(state: AsyncState<string>): string {
  switch (state.status) {
    case 'idle':
      return 'Standby';
    case 'loading':
      return `Syncing (${state.progress}%)`;
    case 'success':
      return `Delivered: ${state.data}`;
    case 'error':
      return `Failed [HTTP ${state.code}]: ${state.message}`;
    default: {
      const _exhaustive: never = state;
      throw new Error(`Unhandled state: ${_exhaustive}`);
    }
  }
}

// 3. 'satisfies' for Literal Preservation without Type Widening
const routeConfig = {
  home: { path: '/', access: 'public' },
  admin: { path: '/admin', access: 'restricted' }
} satisfies Record<string, { path: string; access: 'public' | 'restricted' }>;

// routeConfig.home.path retains literal type "/" rather than widening to 'string'

Core Advantages

  • Discriminated Unions: Narrow object properties safely with standard control flow.
  • Exhaustiveness Checking: Enforce total union coverage using the never type.
  • The satisfies Operator: Validates interfaces while retaining specific literal types.
Union State Machine Visualizer status: "idle"
Narrowed Type State idle
State is idle. No payload or errors active.