TypeScript Union and Intersection Types

Introduction

TypeScript's type system becomes expressive when you compose types instead of creating them from scratch. **Union** and **Intersection** types are two fundamental composition tools: unions express alternatives, intersections express combinations.


Union Types

A **union type** lets a value be one of several types using the `|` operator. Use unions when a variable or parameter can validly hold different shapes or primitives.

Example

// value can be string or number
type Id = string | number;

function formatId(id: Id){
  if (typeof id === "number") return `#${id.toString().padStart(6, "0")}`;
  return id.toUpperCase();
}

Key notes

  • Type narrowing (e.g., `typeof`, `in`, user-defined type guards) is essential to work with unions safely.
  • Unions are **exclusive alternatives**: a value must match at least one member of the union.

Intersection Types

An **intersection type** combines multiple types into one using the `&` operator. The resulting type must satisfy **all** constituent types.

Example

type Timestamped = { createdAt: string };
type User = { id: number; name: string };

type UserRecord = User & Timestamped;

const u: UserRecord = { id: 1, name: "Ava", createdAt: "2026-02-18T12:00:00Z" };

Key notes

  • Intersections are useful to merge capabilities or constraints (e.g., mixins, combining interfaces).
  • If members conflict (same property with incompatible types), the intersection becomes impossible to satisfy.

Combining Patterns and Advanced Usage

You can mix unions and intersections to model complex APIs: e.g., a function that accepts several distinct option shapes (union) where each shape shares some common fields (intersection).

Distributive conditional types and transforms

// map a union to a union of promises
type ToPromise = T extends any ? Promise : never;
type U = "a" | "b";
type PU = ToPromise; // Promise<"a"> | Promise<"b">

Advanced type transforms let you convert unions to intersections and vice versa using conditional types and helper patterns when needed.


Comparison Table

AspectUnionIntersection
Operator|&
MeaningEither one of the typesAll types combined
Use caseAlternative inputs or resultsCompose capabilities or constraints
NarrowingRequired to access specific membersAll members available
Failure modeRuntime checks neededConflicting properties make type impossible

Best Practices

  • Prefer explicit discriminants for unions (e.g., `type A = { kind: "a"; ... } | { kind: "b"; ... }`) to simplify narrowing.
  • Avoid overly broad unions like `any | T`; prefer precise alternatives.
  • Use intersections to express shared capabilities (e.g., `Serializable & Identifiable`).
  • Keep types readable — split complex unions/intersections into named aliases and document intent.

More TS articles:

Interactive fractal graphics

Animated plasma effect

Speech synthesis