TypeScript · Modelling Data

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.

Intermediate 5 min Lesson 9 of 29Examples target TypeScript 5.x with "strict": true.
Before you start:Objects and Type Aliases
0/29 in TypeScript
On this page · 5 min · Intermediate

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#

TypeScript Illustrative
type ID = string | number;
type Timestamped = { createdAt: Date };
type Post = { title: string } & Timestamped;

Code example: Request states as a discriminated union#

TypeScriptRequest states as a discriminated union Illustrative
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' }));
Expected output
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#

  1. ID can be a string or a number; calling a string method directly is rejected.
  2. typeof narrows id to string or number in each branch.
  3. Order is an intersection: it has id, timestamps and total.
  4. RequestState has three members, each with a different status literal.
  5. 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#

Optional properties instead of a union

{ loading: boolean; data?: T; error?: string } allows impossible combinations (loading with an error). A discriminated union makes them impossible.

Intersecting conflicting types

{ id: string } & { id: number } makes id never — no value can satisfy both.

Confusing | and &

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?
type Payment = { method: 'card'; … } | { method: 'upi'; … }
Show solution
TypeScript Illustrative
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#

Union and Intersection Types quizMultiple choice Question 1 of 3

What does string | number mean?

Score so far: 0/0

Want more? Practise all TypeScript MCQs · TypeScript interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.