Union & Intersection Types
A union type says a value can be one type or another. An intersection type says a value is one type and another at the same time. Together they let you build precise types out of smaller pieces.
Union types
function formatId(id: string | number): string { return `ID-${id}`; } console.log(formatId(42)); console.log(formatId("abc"));
ID-42 ID-abc
string | number is read "string or number". Either is accepted, and anything else — a boolean, an object — is rejected.
Only shared members are allowed
With a union, TypeScript only lets you use what's valid for every member, because it can't know which one you have:
function shout(value: string | number) { return value.toUpperCase(); }
error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
toUpperCase exists on strings but not numbers. Methods both types share, like toString(), are fine. To use string-only methods you first narrow the union with a check:
function shout(value: string | number) { if (typeof value === "string") { return value.toUpperCase(); } return value.toFixed(0); } console.log(shout("hey"), shout(3.7));
HEY 4
Lesson 22 is all about narrowing; for now, just know that a typeof check makes TypeScript treat the value as the matching member inside that branch.
Unions of object types
type Circle = { kind: "circle"; radius: number }; type Square = { kind: "square"; side: number }; type Shape = Circle | Square; function area(s: Shape): number { return s.kind === "circle" ? Math.PI * s.radius ** 2 : s.side ** 2; } console.log(area({ kind: "square", side: 3 }));
9
Every member shares a kind property with a different literal value, so checking it tells TypeScript which shape you have.
Arrays of unions vs unions of arrays
Parentheses matter here:
(string | number)[]— one array that may mix strings and numbers:[1, "a", 2].string[] | number[]— either an all-string array or an all-number array, never mixed.
Intersection types
An intersection, written with &, combines several types into one that has all of their properties:
type Timestamped = { createdAt: Date }; type Authored = { author: string }; type Post = { title: string } & Timestamped & Authored; const post: Post = { title: "Hello", author: "Lee", createdAt: new Date("2026-01-01"), }; console.log(`${post.title} by ${post.author}, ${post.createdAt.getFullYear()}`);
Hello by Lee, 2026
This is the type-alias equivalent of interface Post extends Timestamped, Authored, and is a convenient way to mix reusable "trait" types together.
Conflicting intersections
If two parts of an intersection disagree about a property, the property becomes impossible to satisfy:
type A = { id: string }; type B = { id: number }; type AB = A & B; const x: AB = { id: "1" };
error TS2322: Type 'string' is not assignable to type 'never'.
No value is both a string and a number, so id ends up as never. When combining types with &, make sure shared property names agree.
The mental model
It helps to think of types as sets of values. A union is the set of values in either type (larger, so you know less about each value). An intersection is the set of values in both types (smaller, so you know more). That's why a union restricts what you can do with a value, while an intersection lets you do more.