TS Union Types
A union type lets a value be one of several types: A | B | C. It models "this could be either" — the most common shape for real-world data.
Basics
TS
type Id = number | string;
function lookup(id: Id) {
// Common methods are available
return id.toString();
// id.toUpperCase() ✗ — not on number
// id.toFixed(2) ✗ — not on string
}
You can only use members that exist on every type in the union — narrow with a guard before reaching for specifics.
Narrowing
TS
function lookup(id: Id) {
if (typeof id === 'string') {
return id.toUpperCase(); // string
}
return id.toFixed(0); // number
}
Union of literal types
TS
type Direction = 'up' | 'down' | 'left' | 'right';
function move(d: Direction) {
console.log('Moving', d);
}
move('up'); // OK
// move('upward'); // ✗ error
Discriminated unions
Add a literal "tag" field and TS can narrow inside a switch:
TS
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'rect'; w: number; h: number };
function area(s: Shape) {
switch (s.kind) {
case 'circle': return Math.PI * s.r ** 2;
case 'rect': return s.w * s.h;
}
}
Exhaustiveness check
Assign the unmatched case to never — TS will error if you ever add a new variant and forget to handle it:
TS
function area(s: Shape): number {
switch (s.kind) {
case 'circle': return Math.PI * s.r ** 2;
case 'rect': return s.w * s.h;
default: {
const _exhaustive: never = s;
throw new Error('unhandled: ' + JSON.stringify(s));
}
}
}
Tip: Discriminated unions are TypeScript's superpower. Any time you've used a boolean flag like
isLoading: boolean; data?: T; error?: Error, ask whether { status: 'idle' } | { status: 'loading' } | … would be cleaner.Example
Example
type Id = number | string;
function lookup(id: Id) {
return typeof id === 'string' ? id.toLowerCase() : id.toFixed(0);
}
console.log(lookup(42), lookup('ABC'));
Try it Yourself »
Exercise
Operator that joins types in a union.
type Id = number
string;
A single vertical bar.
Discussion
Loading…