Contents

Programming Fundamentals › Type Systems

Discriminated Union

A union whose members are told apart by a tag field.

Also known as: tagged union, sum type, union with tag, discriminant, algebraic data type, tagged unions

A discriminated union is a union of object types, where each member has a field with a unique literal value (the “tag” or “discriminant”) that tells them apart. The type system can then work out which variant you have just by checking that field.

type Request =
  | { status: "loading" }
  | { status: "success"; data: User[] }
  | { status: "error"; message: string };

function render(req: Request) {
  switch (req.status) {
    case "loading": return "Loading…";
    case "success": return `${req.data.length} users`;     // `data` exists only here
    case "error":   return `Failed: ${req.message}`;        // `message` exists only here
  }
}

Inside each case, TypeScript narrows the type (type narrowing), so req.data is only available when status is "success".

Why it’s so useful

Compare with one loose object:

type Loose = { loading: boolean; data?: User[]; error?: string };

That allows impossible states (loading: true with an error, or data missing on success) and forces ? checks everywhere. The discriminated union makes impossible states unrepresentable: you can’t build a “success” without data, or an “error” without message.

It’s ideal for:

  • loading, success and error UI states (loading and error states),
  • API results ({ ok: true, value } | { ok: false, error }),
  • events and actions in reducers ({ type: "added", item } | { type: "removed", id }),
  • finite state machines (UI state machines).

Exhaustiveness checking

Make the compiler tell you when you forget a case, by assigning the leftover value to never:

default: {
  const unreachable: never = req;     // compile error if a new status isn't handled
  throw new Error(`Unhandled: ${unreachable}`);
}

Other languages

The same idea exists as sum types or algebraic data types: Rust and Swift enums with data, Kotlin sealed classes, Haskell data types. Python can approximate it with dataclasses and Literal types plus a type checker. The core principle: model the possibilities explicitly, instead of with a pile of optional fields.