Skip to content

Union and Intersection Types

Real data is often not one fixed type. A value might be a string or a number. A function parameter might be provided or absent. An object might combine properties from two separate shapes. TypeScript handles all of these with union and intersection types.

A union type says a value can be one of several types. The | operator separates the options:

let id: string | number;
id = 'q-001'; // ✓
id = 42; // ✓
id = true; // Error: Type 'boolean' is not assignable to type 'string | number'

TypeScript accepts any assignment that matches at least one of the listed types.

When you have a union type, TypeScript does not let you call methods that only exist on one branch without checking first:

function display(value: string | number): void {
console.log(value.toUpperCase()); // Error: 'toUpperCase' does not exist on type 'number'
}

To use type-specific methods, you narrow the union — check which branch you are in:

function display(value: string | number): void {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // ✓ — TypeScript knows it's string here
} else {
console.log(value.toFixed(2)); // ✓ — TypeScript knows it's number here
}
}

Module 06 covers narrowing in depth. For now, typeof is the tool you reach for with primitive unions.

A common use case is a value that might be absent. Rather than any or an uninitialized variable, you declare the possibility explicitly:

function fetchQuestions(amount: number, difficulty: string | undefined): void {
// difficulty may or may not be provided
}

The shorthand for T | undefined in function parameters is the optional marker ?:

function fetchQuestions(amount: number, difficulty?: string): void {
// difficulty is string | undefined
}

Both forms are equivalent. The ? syntax is more common in parameter lists; the explicit union is clearer in variable declarations.

AceIt’s fetchQuestions function accepts an optional difficulty:

async function fetchQuestions(amount: number, difficulty?: Difficulty): Promise<TriviaQuestion[]> {
let url = `${BASE_URL}?amount=${amount}&type=multiple`;
if (difficulty) url += `&difficulty=${difficulty}`;
// ...
}

When difficulty is undefined, the URL is built without it and the API returns questions of any difficulty. TypeScript ensures you cannot pass an invalid value — only a valid Difficulty or nothing.

An intersection type combines multiple object shapes into one. The & operator says a value must satisfy all of the listed types simultaneously:

type HasId = { id: string };
type HasScore = { score: number };
type ScoredEntry = HasId & HasScore;
// equivalent to: { id: string; score: number }
const entry: ScoredEntry = { id: 'q-001', score: 7 }; // ✓
const bad: ScoredEntry = { id: 'q-001' }; // Error: missing 'score'

Intersection types are most useful when composing reusable object shapes. Rather than repeating common properties in every type definition, you define each concern once and combine them.

The names are counterintuitive at first:

  • Union (|) — the value can be either type. The set of possible values is the union of both types.
  • Intersection (&) — the value must satisfy both types simultaneously. The set of valid properties is the intersection (combination) of both shapes.

Think of it this way: a union is more permissive (either/or); an intersection is more restrictive (must have everything from both).

In types.ts:

  1. Declare a type StringOrNumber = string | number. Write a function normalize(value: StringOrNumber): string that returns the value as a string — use typeof to branch.
  2. Declare type WithTimestamp = { createdAt: string }. Combine it with { score: number; total: number } using & to make a TimestampedScore type. Create a variable of that type.
  3. Write a function buildDifficultyParam(difficulty?: string): string that returns &difficulty=${difficulty} if a value is provided, or '' if not. Handle the undefined case explicitly.
  • Union types (|) allow a value to be one of several types — the value must match at least one.
  • To use type-specific methods on a union, narrow it first with typeof or another check.
  • Optional parameters (?) are shorthand for T | undefined.
  • Intersection types (&) combine object shapes — the value must satisfy all of the listed types.
  • Union is permissive (either/or); intersection is restrictive (must satisfy all).