Discriminated unions

Unions of objects with a shared tag field that makes it easy to tell the variants apart.

On this page

Example

#
TypeScript
type RequestState =
  | { status: 'loading' }
  | { status: 'success'; data: string[] }
  | { status: 'error'; message: string }

function render(state: RequestState): string {
  switch (state.status) {
    case 'loading':
      return 'Loading…'
    case 'success':
      return `Courses: ${state.data.join(', ')}`
    case 'error':
      return `Error: ${state.message}`
  }
}

console.log(render({ status: 'loading' }))
console.log(render({ status: 'success', data: ['HTML', 'CSS'] }))
console.log(render({ status: 'error', message: 'No connection' }))
Result
Loading…
Courses: HTML, CSS
Error: No connection

Definition and usage

#

A discriminated union is a union of object types where every variant has a shared field with a different literal value, for example type: "text" | "image". That field acts as a tag.

After checking the tag (if (block.type === "image"), or a switch) the compiler knows exactly which variant it’s dealing with, and gives you access to its fields.

This is the basic pattern for modeling states (loading, success, error), events, messages, or task types. Combined with never in the default branch, it gives you exhaustiveness checking.

Syntax

#
Syntax
type A = { kind: "a"; … }
type B = { kind: "b"; … }
type Union = A | B

Good practices

#
  • Model state as a discriminated union instead of several loose flags (isLoading, error, data). Impossible combinations stop being possible.
  • Add a default branch with assertNever, so the compiler warns you about unhandled variants.

Related entries

#

Spotted a mistake or missing an example? Write to us.