Union, Intersection, and Literal Types
Introduction
Real-world values often belong to one of several shapes or combine multiple capabilities. TypeScript models that with union (|), intersection (&), and literal types that pin values to exact strings or numbers. These building blocks power discriminated unions, configuration flags, and flexible APIs.
Prerequisites
Union Types: A or B
A value may be one of several types:
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
printId(101);
printId("abc-101");Code explanation:
string | numberaccepts either primitive- You must narrow before using type-specific methods (next chapter)
Union of object shapes:
type Success = { ok: true; data: string };
type Failure = { ok: false; error: string };
type Result = Success | Failure;
function handle(result: Result): void {
if (result.ok) {
console.log(result.data);
} else {
console.log(result.error);
}
}ok acts as a discriminant—TypeScript narrows inside each branch.
Intersection Types: A and B
Combine multiple types into one requirement:
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
const person: Person = {
name: "Ada",
age: 30,
};Code explanation:
Personmust satisfy all members ofNamedandAged- Conflicting property types must be compatible (same or subtype)
Intersection with interfaces
Literal Types
Exact values as types:
type Direction = "north" | "south" | "east" | "west";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function move(dir: Direction): void {
console.log(`Moving ${dir}`);
}
move("north");
// move("up"); // ErrorCode explanation:
- String literal unions model fixed sets of choices
- Numeric literal unions model finite numeric domains
const and literal inference
const theme = "dark" as const;
// theme: "dark" (not string)
const config = {
mode: "production",
port: 3000,
} as const;
// config.mode: "production", config.port: 3000as const makes properties readonly and literal-typed.
Unions vs Intersections
| Operator | Meaning | Example mental model |
|---|---|---|
| union | One of | “string or number” |
& intersection | All of | “named and aged” |
type A = { a: string };
type B = { b: number };
type AB = A & B;
type AorB = A | B;AB requires both a and b. AorB requires either shape (often too loose for objects unless narrowed).
Practical Patterns
Status and HTTP-style codes
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function request(method: HttpMethod, path: string): void {
console.log(`${method} ${path}`);
}Configuration with unions
Discriminant type narrows config in each branch.
Combining unions and intersections
type Admin = { role: "admin"; permissions: string[] };
type Member = { role: "member" };
type Guest = { role: "guest" };
type User = (Admin | Member | Guest) & { id: string; name: string };
function describe(user: User): string {
if (user.role === "admin") {
return `${user.name} (admin, ${user.permissions.length} permissions)`;
}
return `${user.name} (${user.role})`;
}When to Use type vs interface
Unions and most literal combinations require type:
type ID = string | number;
type Theme = "light" | "dark";interface cannot express A | B directly.
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Wide string instead of literals | Lost exhaustiveness | Use union of literals |
| Union of unrelated objects without discriminant | Hard to narrow | Add shared field like kind or type |
A & B with conflicting types | Error or never | Align property types |
| Too many union members | Unreadable | Group or split types |
FAQ
Union vs enum?
Enums name constants at runtime; string literal unions are lightweight and tree-shake friendly. Both work; teams often prefer unions for new code.
Can unions include null?
Yes: string | null. Prefer explicit unions over implicit null everywhere (nullability chapter).
What is never in unions?
A | never simplifies to A. never appears when intersections conflict or exhaustive checks fail.
Is intersection the same as multiple implements?
Similar idea for types. Classes use implements A, B; type aliases use A & B.
How many union members is OK?
Dozens of string literals are fine (for example HTTP status families split across types). If logic explodes, use maps or split modules.
Does order matter in A | B?
No. string | number equals number | string.