06 — Combining Types: Unions, Intersections, and Aliases
"Just some operators between type names" was my combining-types model, and it blurred the one distinction that matters. Writing it down separated the two ideas that everything else hangs off: a union means "or" (a value is one of several types), an intersection means "and" (a value must satisfy all of them at once), and a type alias is how I give the combined result a reusable name. [1] Once the or/and distinction was sharp, most of the type definitions I write stopped feeling mysterious.
The motivation is that real data doesn't fit neatly into one type. A function might accept a string or a number. An object might need to be both a User and a Timestamped record. The primitive types describe single shapes; combining types is how I describe the shapes that actually occur.
Union types: "or"
A union type declares that a value can be one of several types, separated by the pipe (|) [1]:
function format(value: string | number) {
return value.toString();
}value is either a string or a number, and the compiler allows either. The cost is that inside the function I can only safely use operations common to all members of the union until I narrow it down — .toString() works on both, so it's fine, but .toUpperCase() would be an error because numbers don't have it. (Narrowing — how the compiler refines a union inside conditional blocks — gets its own notes.) Unions are the workhorse of flexible APIs: optional values (string | null), polymorphic parameters, and sets of allowed literal values ("GET" | "POST" | "PUT").
Intersection types: "and"
An intersection type combines multiple types into one that has all the properties of each, joined with & [2]:
type TimestampedUser = User & { createdAt: Date };A TimestampedUser must have everything a User has and a createdAt field. The result is the union of all properties — an "and" in the sense of "this and that, together." Intersections shine when composing capabilities: combining a base type with mixins, stacking concerns like Serializable & Cloneable, or extending an imported type without modifying it. The mental image is overlapping sets, where the intersection is the region that satisfies every member.
Type aliases: naming the result
A type alias creates a name for any type — primitive, union, intersection, or object shape [3]:
type ID = string | number;
type Point = { x: number; y: number };The alias doesn't create a new type; it's a new name for an existing one, used for readability and reuse. This matters most for complex unions or intersections that would be painful to spell out at every use site. Naming a union once and reusing the alias keeps signatures short and gives the concept a vocabulary — ID reads better than string | number scattered through a codebase.
The keyof operator: keys as a union
The keyof operator takes an object type and produces a union of its keys [4]:
type User = { id: number; name: string };
type UserKey = keyof User; // "id" | "name"keyof is the bridge between "an object type" and "the set of its property names." It's the foundation for type-safe property access — a function that takes a key and a value can enforce that the key actually exists on the object, and that the value matches that property's type. Almost every generic utility type (and most of the built-in ones) builds on keyof. It's small, but it's the operator that unlocked type-safe metaprogramming for me.
How I use this
My defaults: reach for a union whenever a value can legitimately be one of several shapes — optional fields, polymorphic inputs, sets of allowed strings; reach for an intersection when I'm composing capabilities or extending a type I don't own; and reach for a type alias the moment a union or intersection appears more than once, so it gets a name. The discipline that pays off is naming concepts — Status, ID, Timestamped<T> — because named combinations document intent in a way inline syntax can't. And whenever I see string | number or similar scattered across signatures, that's the signal to lift it into an alias.
References
[1] Microsoft, "Union Types," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types
[2] TypeScript Tutorial, "Intersection Types in TypeScript," 2024. [Online]. Available: https://www.typescripttutorial.net/typescript-tutorial/typescript-intersection-types/
[3] Microsoft, "Type Aliases," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases
[4] Microsoft, "Keyof Type Operator," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#handbook-content
Knowledge check · Question 1 of 5
A parameter typed `string | number` means the value…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!