Union vs Intersection Types
Combining types with the pipe for "one of these" and the ampersand for "all of these at once."
3 min read
Beyond naming a single type, TypeScript lets you build new types by combining existing ones. The two basic combinators — union (|) and intersection (&) — look similar but mean nearly opposite things.
Union types: one of several possibilities
A union describes a value that can be any one of several types:
type ID = string | number;
let userId: ID = "abc123"; // fine
userId = 42; // also fine
userId = true; // Type 'boolean' is not assignable to type 'ID'Unions are especially useful with string literal types, modeling a value restricted to a specific, known set of options:
type Status = "pending" | "active" | "closed";
function updateStatus(status: Status) { /* ... */ }
updateStatus("active"); // fine
updateStatus("archived"); // Argument of type '"archived"' is not assignable to type 'Status'This gives you enum-like safety — autocomplete for the valid options, and a compile error for anything outside the set — without needing an actual enum (covered in the next lesson).
Working with a union: narrowing is required
A union only gives you access to members common to every type in the union, until you narrow it to one specific member:
function formatId(id: string | number): string {
return id.toUpperCase(); // Property 'toUpperCase' does not exist on type 'number'
}
function formatId(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase(); // fine -- narrowed to string here
}
return id.toString();
}That typeof check is a type guard — the mechanics of narrowing get their own lesson shortly.
Intersection types: all of several requirements at once
An intersection combines multiple types into one that must satisfy all of them simultaneously:
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge;
const p: Person = { name: "Ada", age: 36 }; // must have both name AND ageWhere a union says "this could be any one of these," an intersection says "this must be every one of these, combined into a single object."
A common mistake: intersecting incompatible primitives
Intersecting two object types combines their properties, but intersecting two incompatible primitive types produces never — a type with no possible values, since nothing can be a string and a number at once:
type Impossible = string & number; // type is `never` -- no value can satisfy thisThis is rarely written directly on purpose, but shows up as a confusing error when intersecting two object types that both define the same property with conflicting types — worth recognizing if never ever shows up somewhere you didn't expect it.
Choosing between them
Reach for a union when a value is genuinely one thing or another (a status, a result that's either data or an error). Reach for an intersection when you're combining separate, compatible pieces into one complete shape (mixing a base type with additional fields). The next lesson goes deeper into narrowing — the mechanism that makes working with unions safe and precise.