Comparing TypeScript Union vs Intersection Types: When and How to Use Each Safely
Learn the differences between TypeScript union and intersection types, with practical examples and tips to use each safely and avoid common errors.
TypeScript provides powerful tools to create flexible types, and two of the most important are union and intersection types. As a beginner, understanding when and how to use each can help you write safer and more readable code. This article will explain the differences with simple examples and common mistakes to avoid.
### What are Union Types?
A union type allows a variable to hold a value that can be one of multiple types. Think of it as an "OR" operation. You use the pipe (`|`) symbol to define unions.
type StringOrNumber = string | number;
let value: StringOrNumber;
value = 'hello'; // ✅ valid
value = 42; // ✅ valid
// value = true; // ❌ Error: Type 'boolean' is not assignable to type 'string | number'.Union types are useful when you want to allow multiple possible types but don’t require all properties to exist at once.
### What are Intersection Types?
Intersection types combine multiple types into one. Instead of "OR", think of it as an "AND" operation. The ampersand (`&`) symbol creates intersections.
type Person = {
name: string;
};
type Employee = {
employeeId: number;
};
type EmployeePerson = Person & Employee;
let emp: EmployeePerson = {
name: 'Alice', // required by Person
employeeId: 123 // required by Employee
};Intersection types require the variable to satisfy all combined types, meaning it must have all properties from each type.
### When to Use Union Types
Use union types if your variable can be one type OR another. This is common for function parameters that accept multiple types or API responses that can vary.
function printId(id: number | string) {
if (typeof id === 'string') {
console.log('ID as string:', id.toUpperCase());
} else {
console.log('ID as number:', id.toFixed(2));
}
}
printId('abc');
printId(101);The type guard (`typeof`) lets TypeScript safely narrow down the union type and prevent errors like calling a string method on a number.
### When to Use Intersection Types
Use intersection types when you want to merge multiple types into one, ensuring the value has all the properties from each. This is helpful in extending interfaces or mixing traits.
type Drivable = {
drive: () => void;
};
type Flyable = {
fly: () => void;
};
// A vehicle that can both drive and fly
type FlyingCar = Drivable & Flyable;
const myCar: FlyingCar = {
drive: () => console.log('Driving'),
fly: () => console.log('Flying')
};### Common Errors and How to Avoid Them
1. **Assigning incompatible types to unions:** A union only accepts the specified types. Assigning anything outside those will cause errors.
type AorB = 'a' | 'b';
// let c: AorB = 'c'; // Error: Type '"c"' is not assignable to type '"a" | "b"'.2. **Mistaking intersection for union:** Intersection requires *all* properties, so missing one will cause an error.
type T1 = { a: number } & { b: string };
// let obj: T1 = { a: 10 }; // Error: Property 'b' is missing
let obj: T1 = { a: 10, b: 'hello' }; // Correct3. **Not using type guards with unions:** Calling properties that exist only in some union members will cause errors unless checked.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
// animal.swim(); // ❌ Error: Property 'swim' does not exist on type 'Fish | Bird'.
if ('swim' in animal) {
animal.swim(); // ✅ Safe
} else {
animal.fly();
}
}### Summary
• Use **union types (`|`)** when a variable can be this type OR that type. • Use **intersection types (`&`)** when a variable should have combined properties from multiple types. • Always use **type guards** to safely access members of union types. Mastering these concepts will help you avoid type errors and write robust TypeScript code as you build larger projects.