Union and Intersection Types
Real data is often “this OR that”: an ID that is a string or a number, a request that is loading, successful or failed. Union and intersection types describe exactly that.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
Learning objectives#
- Write union types with |
- Combine types with &
- Model states with discriminated unions
- Know which members you can use on a union
Explanation#
A union A | B means “either an A or a B”. let id: string | number accepts both.
On a union you can only use what all members have in common. To use string-only methods, first check typeof id === "string" (narrowing).
An intersection A & B means “both A and B at once” — an object with all properties of both.
Discriminated unions: give each member a common literal property (often status or kind). Checking it tells TypeScript exactly which member you have.
Syntax#
type ID = string | number;
type Timestamped = { createdAt: Date };
type Post = { title: string } & Timestamped;
Code example: Request states as a discriminated union#
type ID = string | number;
function formatId(id: ID): string {
// id.toUpperCase(); // ✗ Property 'toUpperCase' does not exist on type 'number'
return typeof id === 'string' ? id.toUpperCase() : id.toString().padStart(5, '0');
}
type HasId = { id: number };
type HasTimestamps = { createdAt: Date; updatedAt: Date };
type Order = HasId & HasTimestamps & { total: number };
type RequestState<T> =
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
function render(state: RequestState<Order[]>): string {
switch (state.status) {
case 'loading':
return 'Loading…';
case 'success':
return `${state.data.length} orders`; // data exists here
case 'error':
return `Failed: ${state.message}`; // message exists here
}
}
console.log(formatId('ab12'), formatId(42));
console.log(render({ status: 'error', message: 'Timeout' }));
AB12 00042 Failed: Timeout
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- ID can be a string or a number; calling a string method directly is rejected.
- typeof narrows id to string or number in each branch.
- Order is an intersection: it has id, timestamps and total.
- RequestState has three members, each with a different status literal.
- Inside each case, TypeScript knows which properties exist.
Real-world use case#
Front-end apps model API calls as loading | success | error; Redux actions are discriminated unions on their type field.
Common mistakes#
{ loading: boolean; data?: T; error?: string } allows impossible combinations (loading with an error). A discriminated union makes them impossible.
{ id: string } & { id: number } makes id never — no value can satisfy both.
For object types, A | B has fewer guaranteed properties; A & B has more. Think “or” vs “and”.
Best practices#
- Model mutually exclusive states with discriminated unions
- Use a literal kind/status/type field as the discriminator
- Use & to compose reusable pieces (timestamps, ids)
- Handle every union member — let the compiler help (see Type Narrowing)
Remember this#
| = OR (one of these). & = AND (all of these together).
On a union, you can only touch what every member shares — until you narrow.
Discriminated union = shared “status” label tells TS which shape it is.
Practice exercise#
Your task: Model a payment method that is either a card (cardNumber, expiry) or UPI (upiId), with a "method" discriminator, and write describe(p) returning a string for each.
Need a hint?
Show solution
type Payment =
| { method: 'card'; cardNumber: string; expiry: string }
| { method: 'upi'; upiId: string };
function describe(p: Payment): string {
return p.method === 'card' ? `Card ending ${p.cardNumber.slice(-4)}` : `UPI ${p.upiId}`;
}
Mini quiz#
What does string | number mean?
Want more? Practise all TypeScript MCQs · TypeScript interview questions
My notes#
Saved in this browser.