---
id: wiki-2026-0508-type-safe-error-handling-exhaust
title: Type-safe Error Handling & Exhaustiveness Checking
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Result Type, Discriminated Union Errors, Never-check]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, error-handling, type-system, patterns]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: TypeScript
  framework: neverthrow/effect-ts
---

# Type-safe Error Handling & Exhaustiveness Checking

## 매 한 줄
> **"매 error 의 value 의 represent — 매 compiler 의 case 의 빠짐 없는 handling 강제"**. 매 throw 의 type-erased — 매 caller 의 매 catch 의 X. 매 Result/Either + discriminated union + `never` exhaustiveness check 의 type-safe error flow 의 핵심. 2026 매 `effect-ts` / `neverthrow` 의 production-ready.

## 매 핵심

### 매 매 Throw 의 문제
- TypeScript 매 `throws` clause X — 매 function signature 의 error type 의 X.
- 매 caller 의 catch X 의 silent crash.
- 매 `unknown` catch — 매 narrowing 부담.

### 매 Result/Either 패턴
- `Result<T, E> = { ok: true, value: T } | { ok: false, error: E }`.
- 매 error 의 type-checked.
- 매 caller 의 매 두 case handle 강제.

### 매 Discriminated Union 의 error type
- 매 multiple error variant — `kind` discriminator.
- 매 narrowing 의 자동.

### 매 Exhaustiveness via `never`
- `switch` 의 모든 case 의 handle X 의 compile error.
- `assertNever(value: never)` 패턴.

### 매 응용
1. API client — 매 network/parse/4xx/5xx 의 분리.
2. State machine — 매 invalid transition 의 compile-time 검출.
3. Parser — 매 syntax error variant 의 exhaustive handling.
4. Domain logic — 매 business rule violation 의 typed.

## 💻 패턴

### Result type — minimal
```typescript
type Result<T, E> =
  | { ok: true; value: T }
  | { ok: false; error: E };

const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
```

### Discriminated union error
```typescript
type FetchError =
  | { kind: "network"; cause: unknown }
  | { kind: "parse"; raw: string }
  | { kind: "http"; status: number };

async function fetchUser(id: string): Promise<Result<User, FetchError>> {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) return err({ kind: "http", status: res.status });
    const text = await res.text();
    try {
      return ok(JSON.parse(text) as User);
    } catch {
      return err({ kind: "parse", raw: text });
    }
  } catch (cause) {
    return err({ kind: "network", cause });
  }
}
```

### `assertNever` — exhaustiveness
```typescript
function assertNever(x: never): never {
  throw new Error(`Unhandled: ${JSON.stringify(x)}`);
}

function describe(e: FetchError): string {
  switch (e.kind) {
    case "network": return "Network down";
    case "parse":   return `Bad JSON: ${e.raw.slice(0, 50)}`;
    case "http":    return `HTTP ${e.status}`;
    default:        return assertNever(e);
  }
}
```

### `neverthrow` — production library
```typescript
import { ResultAsync, ok, err } from "neverthrow";

function getUser(id: string): ResultAsync<User, FetchError> {
  return ResultAsync.fromPromise(
    fetch(`/api/users/${id}`).then(r => r.json()),
    (e): FetchError => ({ kind: "network", cause: e })
  );
}

const result = await getUser("u1")
  .map(u => u.name)
  .mapErr(e => `Error: ${e.kind}`)
  .match(
    name => `Hello ${name}`,
    msg => msg
  );
```

### Effect-TS — modern alternative
```typescript
import { Effect } from "effect";

class NetworkError { readonly _tag = "NetworkError"; }
class ParseError   { readonly _tag = "ParseError"; }

const fetchUser = (id: string) =>
  Effect.tryPromise({
    try: () => fetch(`/api/users/${id}`).then(r => r.json()),
    catch: () => new NetworkError(),
  });

const program = fetchUser("u1").pipe(
  Effect.catchTag("NetworkError", () => Effect.succeed({ name: "Guest" }))
);
```

### Zod — runtime + compile-time
```typescript
import { z } from "zod";

const UserSchema = z.object({ id: z.string(), name: z.string() });
type User = z.infer<typeof UserSchema>;

function parseUser(raw: unknown): Result<User, z.ZodError> {
  const r = UserSchema.safeParse(raw);
  return r.success ? ok(r.data) : err(r.error);
}
```

### State machine — exhaustive transition
```typescript
type State =
  | { kind: "idle" }
  | { kind: "loading" }
  | { kind: "success"; data: string }
  | { kind: "error"; msg: string };

type Event = { kind: "FETCH" } | { kind: "RESOLVE"; data: string } | { kind: "REJECT"; msg: string };

function transition(s: State, e: Event): State {
  switch (e.kind) {
    case "FETCH":   return s.kind === "idle" ? { kind: "loading" } : s;
    case "RESOLVE": return { kind: "success", data: e.data };
    case "REJECT":  return { kind: "error", msg: e.msg };
    default:        return assertNever(e);
  }
}
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| 단순 fail/success | `Result<T, Error>` (custom). |
| Multi-error variant | Discriminated union + `kind`. |
| Library / framework | `neverthrow`. |
| Complex async / DI / retry | `effect-ts`. |
| Boundary input validation | Zod / Valibot. |
| 매 critical loop (perf) | Throw — 매 zero allocation. |

**기본값**: Discriminated union + `Result<T, E>` + `assertNever`. 매 ecosystem 의 `neverthrow` 채택.

## 🔗 Graph
- 부모: [[TypeScript 타입 시스템 (TypeScript Type System)|Type System]] · [[Error Handling]]
- 응용: [[State Machine]] · [[API Client]]
- Adjacent: [[Discriminated Union]] · [[effect-ts]] · [[Zod]]

## 🤖 LLM 활용
**언제**: 매 multi-error business logic, API boundary, state machine.
**언제 X**: 매 truly unrecoverable (OOM, asserts) — throw 적합. 매 hot loop — 매 allocation cost.

## ❌ 안티패턴
- **매 `throw` 의 silent**: 매 caller 의 catch X.
- **매 `Error` string 의 비교**: 매 fragile — discriminated union 사용.
- **매 default case 의 silent ignore**: 매 `assertNever` 의 X — 매 새 variant 추가 의 silent break.
- **매 `any` / `unknown` 의 catch param 의 직접 사용**: 매 narrowing X.
- **매 Result 의 nested .map 의 callback hell**: 매 `effect-ts` / `do-notation` 사용.

## 🧪 검증 / 중복
- Verified (TypeScript Handbook, neverthrow docs, Effect-TS docs).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Result + discriminated union + exhaustiveness 패턴 |
