stackademic

The leading education platform for anyone with an interest in software development.

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