TypeScript Union and Intersection Types
Union and intersection types model "or" and "and" relationships. Discriminated unions are the most powerful pattern.
Union types
A union type allows a value to be one of several types. Use | to separate them.
type StringOrNumber = string | number;
function display(value: StringOrNumber) {
console.log(value);
}
// String literal unions (most common)
type Direction = "north" | "south" | "east" | "west";
type Status = "loading" | "success" | "error";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function setStatus(status: Status) { ... }
setStatus("loading"); // OK
setStatus("pending"); // Error: not in unionDiscriminated unions
A discriminated union uses a shared literal property to narrow between union members.
type LoadingState = {
status: "loading";
};
type SuccessState = {
status: "success";
data: string[];
};
type ErrorState = {
status: "error";
error: string;
};
type State = LoadingState | SuccessState | ErrorState;
function render(state: State) {
switch (state.status) {
case "loading":
return "Loading...";
case "success":
return state.data.join(", "); // TypeScript knows data exists
case "error":
return `Error: ${state.error}`; // TypeScript knows error exists
}
}Intersection types
An intersection type combines multiple types — the value must satisfy ALL of them.
type Named = { name: string };
type Aged = { age: number };
type Employee = Named & Aged & { department: string };
const e: Employee = {
name: "Alice",
age: 30,
department: "Engineering",
};
// Mixin pattern
type WithTimestamps<T> = T & {
createdAt: Date;
updatedAt: Date;
};
type UserWithTimestamps = WithTimestamps<User>;Type narrowing
TypeScript narrows union types inside conditional blocks using type guards.
function processInput(value: string | number) {
if (typeof value === "string") {
// TypeScript knows value is string here
return value.toUpperCase();
} else {
// TypeScript knows value is number here
return value.toFixed(2);
}
}
// instanceof narrowing
function handle(error: Error | string) {
if (error instanceof Error) {
console.log(error.message); // Error properties available
} else {
console.log(error);
}
}
// in operator narrowing
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function makeNoise(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}Exam tip
Discriminated unions are the most important TypeScript pattern for managing state. Always model async states (loading/success/error) as a discriminated union — it makes the impossible states unrepresentable.
Think you're ready? Prove it.
Take the free TypeScript readiness test. Get a score, topic breakdown, and your exact weak areas.
Take the free TypeScript test →Free · No sign-up · Instant results