Union and Intersection Types in TypeScript
Welcome! Union and intersection types are powerful features that let you combine types in flexible ways. This guide will show you how to use them effectively! 🚀
Union Types
Union types allow a value to be one of several types.
Basic Union Syntax
Use the | (pipe) operator to create union types:
type ID = string | number;
let userId: ID = "user-123"; // ✅ OkuserId = 456; // ✅ Ok// userId = true; // ❌ Error: boolean is not assignableCommon Use Cases
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending"; // ✅ Ok// orderStatus = "cancelled"; // ❌ Error: Not in union
type ResponseData = string | null;
let response: ResponseData = "Success";response = null; // ✅ OkUnion with Multiple Types
type MixedValue = string | number | boolean;
let value: MixedValue = "hello";value = 42;value = true; // All valid!Type Narrowing
When working with unions, TypeScript needs to know which specific type you’re using. This is called type narrowing.
Using typeof
function printValue(value: string | number) { if (typeof value === "string") { console.log(value.toUpperCase()); // TypeScript knows it's a string } else { console.log(value.toFixed(2)); // TypeScript knows it's a number }}
printValue("hello"); // "HELLO"printValue(3.14159); // "3.14"Using instanceof
function process(input: Date | string) { if (input instanceof Date) { console.log(input.getFullYear()); // TypeScript knows it's a Date } else { console.log(input.toUpperCase()); // TypeScript knows it's a string }}Using in Operator
type Dog = { bark: () => void };type Cat = { meow: () => void };
function makeSound(animal: Dog | Cat) { if ("bark" in animal) { animal.bark(); // TypeScript knows it's a Dog } else { animal.meow(); // TypeScript knows it's a Cat }}Discriminated Unions
Discriminated unions use a common property to distinguish between types. This is a powerful pattern!
Basic Example
type SuccessResponse = { status: "success"; data: any;};
type ErrorResponse = { status: "error"; message: string;};
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) { if (response.status === "success") { console.log("Data:", response.data); // TypeScript knows it's SuccessResponse } else { console.log("Error:", response.message); // TypeScript knows it's ErrorResponse }}Advanced Discriminated Union
type Circle = { kind: "circle"; radius: number;};
type Rectangle = { kind: "rectangle"; width: number; height: number;};
type Triangle = { kind: "triangle"; base: number; height: number;};
type Shape = Circle | Rectangle | Triangle;
function calculateArea(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "rectangle": return shape.width * shape.height; case "triangle": return (shape.base * shape.height) / 2; }}
let circle: Circle = { kind: "circle", radius: 5 };console.log(calculateArea(circle)); // 78.54Intersection Types
Intersection types combine multiple types into one. The result has all properties from all types.
Basic Intersection Syntax
Use the & (ampersand) operator:
type Person = { name: string; age: number;};
type Employee = { employeeId: string; department: string;};
type Staff = Person & Employee;
let staff: Staff = { name: "Alice", age: 30, employeeId: "E123", department: "Engineering"};Combining Interfaces
interface HasName { name: string;}
interface HasAge { age: number;}
type User = HasName & HasAge;
let user: User = { name: "Bob", age: 25};Intersection with Functions
type Logger = { log: (message: string) => void;};
type Formatter = { format: (data: any) => string;};
type LoggerWithFormatter = Logger & Formatter;
let consoleLogger: LoggerWithFormatter = { log: (msg) => console.log(msg), format: (data) => JSON.stringify(data)};Union vs Intersection
Understanding the difference is crucial:
| Feature | Union (|) | Intersection (&) |
|---------|-------------|-------------------|
| Meaning | Value can be one of the types | Value must have all properties |
| Properties | Only common properties accessible | All properties from all types |
| Use Case | Value could be different types | Combining multiple behaviors |
Union Example
type A = { name: string };type B = { age: number };
type UnionType = A | B;
let value1: UnionType = { name: "Alice" }; // ✅ Ok (only A)let value2: UnionType = { age: 25 }; // ✅ Ok (only B)let value3: UnionType = { name: "Bob", age: 30 }; // ✅ Ok (both)Intersection Example
type A = { name: string };type B = { age: number };
type IntersectionType = A & B;
let value: IntersectionType = { name: "Alice", age: 25 }; // ✅ Must have both// let invalid: IntersectionType = { name: "Bob" }; // ❌ Error: missing 'age'Practical Examples
Form State Management
type IdleState = { status: "idle" };type LoadingState = { status: "loading" };type SuccessState = { status: "success"; data: any };type ErrorState = { status: "error"; error: string };
type FormState = IdleState | LoadingState | SuccessState | ErrorState;
function renderForm(state: FormState) { switch (state.status) { case "idle": return "Ready to submit"; case "loading": return "Submitting..."; case "success": return `Success: ${state.data}`; case "error": return `Error: ${state.error}`; }}API Request/Response
type BaseRequest = { timestamp: number; userId: string;};
type PostData = { method: "POST"; body: string;};
type GetData = { method: "GET"; params: Record<string, string>;};
type ApiRequest = BaseRequest & (PostData | GetData);
let postRequest: ApiRequest = { timestamp: Date.now(), userId: "123", method: "POST", body: JSON.stringify({ name: "Alice" })};User Permissions
type ReadPermission = { canRead: true };type WritePermission = { canWrite: true };type DeletePermission = { canDelete: true };
type AdminUser = ReadPermission & WritePermission & DeletePermission;type EditorUser = ReadPermission & WritePermission;type ViewerUser = ReadPermission;
let admin: AdminUser = { canRead: true, canWrite: true, canDelete: true};Key Takeaways
- Union types (
|) represent a value that can be one of several types - Intersection types (
&) combine multiple types into one - Use type narrowing (
typeof,instanceof,in) to work with unions safely - Discriminated unions use a common property (like
statusorkind) for type narrowing - Unions are for “either/or” scenarios, intersections for “all together” scenarios
- These patterns enable flexible, type-safe code
💡 Conclusion
Union and intersection types are fundamental to writing expressive TypeScript code. Mastering them will help you model complex data structures and create robust type-safe applications!
Happy coding with TypeScript! 🎉