///

TypeScript Exhaustive Switch with never Capsule

---

///

kind: capsule status: active visibility: private license: fair-use-summary summary: Discriminated union switch에서 default에 assertNever(x: never) 추가하면 새 variant 추가 시 컴파일 타임에 누락 감지. tags: - typescript - type-safety - pattern - capsule related: - personal_vault/knowledge/dev/typescript-advanced.md - personal_vault/knowledge/dev/typescript.md - personal_vault/projects/ops/librarian/capsules/TypeScript Practical Reference Capsule.md


TypeScript Exhaustive Switch with never Capsule

Core claim#

Discriminated union을 switch로 분기할 때, default 블록에서 never 타입으로 변수를 소비하는 헬퍼를 호출하면 union에 새 variant가 추가됐을 때 TypeScript가 컴파일 에러를 낸다. 런타임 누락을 타입 시스템으로 잡는 표준적인 exhaustiveness checking 패턴이다.

When to apply#

  • redux/zustand action union, API response discriminated union, state machine state
  • 도메인이 앞으로 확장될 가능성이 있고, 모든 consumer가 새 케이스를 처리해야 하는 경우

Recipe#

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "triangle"; base: number; height: number };

function assertNever(x: never): never {
  throw new Error(`Unhandled variant: ${JSON.stringify(x)}`);
}

function area(s: Shape): number {
  switch (s.kind) {
    case "circle":   return Math.PI * s.radius ** 2;
    case "square":   return s.side ** 2;
    case "triangle": return (s.base * s.height) / 2;
    default:         return assertNever(s);
  }
}

새 variant를 추가하고 case를 빠뜨리면 default 블록의 s가 더 이상 never가 아니므로 assertNever(s)에서 TS2345 계열의 오류가 난다.

동작 원리#

  • case가 discriminant(kind) 기준으로 narrow된다.
  • 모든 variant가 처리되면 default 블록에서 s의 타입은 never가 된다.
  • 새 variant 추가 후 해당 case를 누락하면 default 블록의 s가 그 새 variant 타입으로 남는다.
  • never 파라미터에 non-never 값을 넘기려 하므로 컴파일 에러가 발생한다.

Caveats#

  • strictNullChecks는 TypeScript의 다른 exhaustive-checking 방식(예: 명시적 반환 타입과 누락 return 감지)에서 특히 중요하지만, assertNever 패턴 자체의 핵심 전제는 discriminated union이 올바르게 narrow되는 것이다. 실무에서는 strict: true와 함께 쓰는 것을 권장한다.
  • if / else if 체인에서도 동일 패턴을 적용할 수 있다. 마지막 else에서 assertNever(value)를 호출한다.
  • assertNever가 실제로 throw하므로 런타임에서도 방어선이 된다. 다만 이 경로에 도달했다면 이미 타입 경계 밖의 값 유입 또는 컴파일 누락이 발생한 버그로 봐야 한다.
  • default 없이 모든 case를 나열하는 스타일을 선호하는 팀도 있다. 누락 감지를 강하게 보장하려면 defaultassertNever 또는 switch 이후 const _: never = value 같은 명시적 never 체크를 둔다.

Source#

  • TypeScript Handbook, “Discriminated Unions / Exhaustiveness checking”: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking
  • Vault cross-check: personal_vault/knowledge/dev/typescript.md의 “exhaustive check” 예시는 default: const _: never = s 형태로 같은 원리를 사용한다.
  • 조회일: 2026-05-13

Sagwan Revalidation 2026-04-19T01:49:53Z#

  • verdict: ok
  • note: TypeScript exhaustive switch + assertNever 패턴은 TS 4.x~5.x 모두 동일하게 동작하며 현재도 표준 권장 방식이다.

Sagwan Revalidation 2026-04-20T02:27:42Z#

  • verdict: ok
  • note: TypeScript exhaustive switch + assertNever 패턴은 현재도 표준 관행이며, 코드·설명 모두 정확하고 최신 practice와 일치한다.

Sagwan Revalidation 2026-04-21T02:43:43Z#

  • verdict: ok
  • note: LLM unavailable: [CLI 오류 1] SessionEnd hook [node "/home/insu/.pixel-agents/hooks/claude-hook.js"] failed: node:internal/modules/cjs/load

Sagwan Revalidation 2026-04-22T03:14:33Z#

  • verdict: ok
  • note: LLM unavailable: [CLI 오류 1] SessionEnd hook [node "/home/insu/.pixel-agents/hooks/claude-hook.js"] failed: node:internal/modules/cjs/load

Sagwan Revalidation 2026-05-13#

  • verdict: revise
  • note: 핵심 주장은 유지. Caveats의 strictNullChecks 필수 표현을 strict: true 권장, assertNever 자체의 필수 조건은 아님으로 완화함.