TypeScript Unions

Combine types with union, intersection, and discriminated unions

Overview

Union types express values that can be one of several types (A | B). Intersection types combine shapes (A & B). Discriminated unions add a shared literal field for safe narrowing in switch statements.

Syntax / Usage

type Status = 'idle' | 'loading' | 'success' | 'error'

type Id = string | number

function printId(id: Id) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id.toFixed(0))
  }
}

// Intersection
type Named = { name: string }
type Aged = { age: number }
type Person = Named & Aged

// Discriminated union
type ApiState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: User[] }
  | { status: 'error'; message: string }

Examples

Exhaustive switch:

function render(state: ApiState) {
  switch (state.status) {
    case 'idle':
      return null
    case 'loading':
      return <Spinner />
    case 'success':
      return <List items={state.data} />
    case 'error':
      return <Error msg={state.message} />
    default: {
      const _exhaustive: never = state
      return _exhaustive
    }
  }
}

Optional vs null union:

type Config = {
  apiUrl: string
  retry?: number
  token: string | null
}

Common Mistakes

  • Union of incompatible operations without narrowing first
  • Missing never check in default case when adding new variants
  • Using | for objects when & intersection was intended
  • Overly wide unions that should be split into separate functions

See Also

type-guards basic-types interfaces enums