Back to TypeScript tutorials
Intermediate12 min read

Union & Intersection Types

Combine types with unions and intersections for flexible, precise modeling of real-world data shapes.

Union Types

Union types (`A | B`) express values that can be one of several types. Narrow unions with typeof checks, instanceof, in operator, or custom type guards before accessing type-specific properties.

String literal unions model enums without runtime cost: `type Theme = "light" | "dark"`. Exhaustiveness checking with `never` in switch default branches catches missing cases when unions grow.

  • `|` for one-of types
  • Narrow before accessing specific props
  • Literal unions for fixed string sets
type Input = string | number;
function format(value: Input) {
  if (typeof value === 'string') return value.trim();
  return value.toFixed(2);
}

Type Guards

User-defined type guards use `value is Type` return annotation: `function isUser(x: unknown): x is User`. The compiler narrows types in true branches after calling the guard.

Discriminated unions add a common literal field (`kind`, `type`, `status`) so switch statements narrow automatically without casts. This pattern models API responses, Redux actions, and state machines cleanly.

  • Custom guards with `x is Type`
  • Discriminated unions with literal field
  • Exhaustive switch with `never`
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rect'; width: number; height: number };

Intersection Types

Intersection types (`A & B`) combine multiple types — values must satisfy all members. Use to mix capabilities: `type Timestamped = Entity & { createdAt: Date }`.

Excessive intersections become hard to read. When combining many interfaces, consider a single interface extending others or a builder pattern for configuration objects.

  • `&` requires all members
  • Mix capabilities onto base types
  • Avoid overly long intersection chains
type Admin = User & { permissions: Permission[] };

Nullable and Optional Unions

Explicit `T | null | undefined` documents nullable APIs. With `strictNullChecks`, null and undefined are not assignable to other types without narrowing — eliminating a huge class of runtime errors.

Use optional chaining and nullish coalescing in application code. At boundaries, parse and validate so inner layers receive non-null types rather than sprinkling checks everywhere.

  • `strictNullChecks` catches null access
  • Validate at boundaries
  • Optional chaining for safe access
function getName(user: User | null): string {
  return user?.name ?? 'Guest';
}

Modeling API Responses

Model success and error responses as discriminated unions rather than optional error fields on success objects. Callers handle each variant explicitly, and the compiler ensures all cases are covered.

Avoid `any` escape hatches on error paths — type the error payload so UI layers render appropriate messages without string parsing.

  • Discriminated unions for success/error
  • Cover all variants in handlers
  • Type error payloads explicitly

Get In Touch


Ready to discuss your next project? Drop me a message.