Glossary / Discriminated unions

Discriminated unions

A discriminated union is a union of object types that share a property whose literal value identifies the variant. TypeScript uses that discriminant to narrow the remaining properties, making state-dependent data explicit and enabling exhaustive handling.

Modeling and narrowing

type Result<T> =
  | { status: 'ok'; value: T }
  | { status: 'error'; error: Error };

function describe<T>(result: Result<T>) {
  if (result.status === 'ok') return String(result.value);
  return result.error.message;
}

The discriminant should be stable and uniquely identify variants. switch statements are often clear. An exhaustive never check makes additions to the union visible at compile time:

function assertNever(value: never): never { throw new Error(String(value)); }

After all cases, passing the residual value to assertNever errors if a new variant was added but not handled. TypeScript can lose narrowing when values are mutated, aliased in unsupported ways, or passed through generic abstractions; immutable data and narrow control flow help.

Discriminated unions model valid state combinations, preventing impossible combinations such as loading: true alongside stale error and success fields. They do not validate JSON: parse external input as unknown and check the tag and payload at runtime.

Interview questions

  1. What is the discriminant? A shared property with literal values that identifies each union member.
  2. How enforce exhaustive handling? Check the residual value as never after covering all variants.
  3. Why preferable to several booleans? It can encode mutually exclusive states and prevent invalid combinations.
  4. Does a typed union validate network data? No; runtime validation is required.
  5. What can weaken narrowing? Mutation, broad types, or control flow the checker cannot prove.

References