Skip to content

Unions & Intersections

Union: a value that is one of several types

Section titled “Union: a value that is one of several types”

A union A | B describes a value that is either an A or a B. In the sets model it’s the set union — so a union is always wider than its members.

type Id = string | number;
let id: Id = "abc";
id = 123; // ✅ both are members of the union

The catch: while you hold a union, you may only touch the members that all branches share, because the compiler can’t know which one you have yet.

function printId(id: string | number) {
id.toUpperCase();
// ❌ toUpperCase does not exist on number.
// Only members common to string AND number are available here.
}

To use type-specific members, you must narrow — the whole next lesson. Until then, a union only exposes the common surface.

Intersection: a value that is all of several types

Section titled “Intersection: a value that is all of several types”

An intersection A & B describes a value that is both an A and a B at once. For object types this means “has every property of both” — so an intersection of objects is narrower (more requirements, fewer valid values).

type WithId = { id: string };
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type Entity = WithId & WithTimestamps;
// must have id AND createdAt AND updatedAt
const e: Entity = { id: "1", createdAt: new Date(), updatedAt: new Date() };

This is the idiomatic way to compose object types from small reusable pieces — far more flexible than inheritance because you can mix any combination.

flowchart TB
  subgraph un["A OR B  (union)"]
    u["more values allowed
fewer members guaranteed"]
  end
  subgraph inter["A AND B  (intersection)"]
    i["fewer values allowed
more members guaranteed"]
  end
Union widens, intersection narrows

Notice the trade in each direction:

  • A union allows more values but guarantees fewer members you can safely access (only the common ones).
  • An intersection allows fewer values but guarantees more members (all of them).
  • string & number is never — no value is both, so the intersection is the empty set.
  • A union with any collapses to any; a union with never drops the never (T | never is just T, because adding the empty set changes nothing).
  • Intersecting object types with a conflicting primitive property can produce never for that property.
type A = { kind: "a"; value: number };
type B = { kind: "b"; value: string };
type Both = A & B;
// kind: "a" & "b" → never; this type is effectively unconstructable

That last one is a hint: when you want “one of these object shapes,” you almost always want a union (A | B) with a shared discriminant field — not an intersection. That’s the discriminated-union pattern in the narrowing lesson.

While holding a value of type `string | number`, which members can you access without narrowing?
What does an intersection of two object types `A & B` require?
Which is wider (allows more values): a union or an intersection?
You want "a value that is one of several object shapes." Which should you usually reach for?