# Narrowing and discriminated unions

> Let TypeScript prove which case you’re in from a check you already wrote, and catch the case you forgot.

Concept · Building apps · https://levelcode.org/concepts/type-narrowing

A union type such as `string | number` or `User | null` says "one of these." **Narrowing** is how TypeScript works out *which* one from the checks in your code. Inside the `if`, the variable has the narrower type.

```ts
function format(value: string | number | null) {
  if (value === null) return '—'          // value: null
  if (typeof value === 'string') return value.trim() // value: string
  return value.toFixed(2)                 // value: number — the only case left
}
```

TypeScript understands `typeof`, `instanceof`, `in`, equality checks, truthiness, `Array.isArray`, and early `return`/`throw`.

## Discriminated unions

Give every variant a shared literal field, the **discriminant**, and a single check narrows the whole object:

```tsx
type RequestState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: Order[] }
  | { status: 'error'; error: string }

function render(s: RequestState) {
  switch (s.status) {
    case 'idle': return null
    case 'loading': return <Spinner />
    case 'success': return <OrderList orders={s.data} /> // data exists only here
    case 'error': return <Alert>{s.error}</Alert>
  }
}
```

This makes **impossible states impossible**. With separate `isLoading`, `data` and `error` fields you can end up with `isLoading: true` *and* an error. With a union you can't.

## Exhaustiveness checks

Make the compiler tell you when you add a variant and forget to handle it:

```ts
default: {
  const unreachable: never = s   // error if any status isn't handled above
  throw new Error(`unhandled: ${JSON.stringify(unreachable)}`)
}
```

## Custom type guards and their limit

```ts
function isOrder(x: unknown): x is Order {
  return typeof x === 'object' && x !== null && 'id' in x && 'total' in x
}
```

A type guard is a promise you make to the compiler, and TypeScript doesn't check it. For data from outside your program, such as API responses, `localStorage` or [model output](https://levelcode.org/concepts/structured-outputs), validate at runtime with a schema library like Zod, and let it produce the narrowed type.
