Union, Intersection & Literal Types
📖 Concept
Union types (A | B) represent values that can be ONE OF several types. Think of it as "either/or".
Intersection types (A & B) combine multiple types into one that has ALL properties. Think of it as "both/and".
Literal types narrow a type to a specific value — not just string, but exactly "hello".
Union types are everywhere in production TypeScript:
- API response states:
"loading" | "success" | "error" - Function parameters:
string | number - Nullable values:
string | null
Intersection types combine shapes:
User & Admin= an object with ALL properties from both- Used for mixins, extending types, and combining capabilities
Literal types give extreme precision:
"GET" | "POST" | "PUT" | "DELETE"— only these exact strings are allowed1 | 2 | 3— only these exact numbers- Combined with unions, they create discriminated unions — one of TypeScript's most powerful patterns
Discriminated Unions: A union where each member has a common property (the "discriminant") with a unique literal type. TypeScript uses this to narrow the union in switch/if statements.
🏠 Real-world analogy: A union type is like a parking space labeled "Cars OR Motorcycles" — it accepts either. An intersection type is like a job requirement "Must have Engineering AND MBA" — need both. Literal types are like a vending machine button for a specific item — only that exact selection works.
💻 Code Example
1// Union types — either/or2type StringOrNumber = string | number;3function format(value: StringOrNumber): string {4 if (typeof value === "string") {5 return value.toUpperCase(); // TS knows it's string here6 }7 return value.toFixed(2); // TS knows it's number here8}910// Literal types — exact values11type Direction = "north" | "south" | "east" | "west";12type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";13type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;1415function move(direction: Direction): void {16 console.log(`Moving ${direction}`);17}18move("north"); // ✅19// move("up"); // ❌ Argument of type '"up"' is not assignable2021// Intersection types — combine all properties22interface HasName { name: string; }23interface HasAge { age: number; }24interface HasEmail { email: string; }2526type Person = HasName & HasAge;27type ContactablePerson = Person & HasEmail;2829const person: ContactablePerson = {30 name: "Alice",31 age: 30,32 email: "alice@example.com"33};3435// ⭐ Discriminated Union — TypeScript's killer pattern36type LoadingState = { status: "loading" };37type SuccessState = { status: "success"; data: string[] };38type ErrorState = { status: "error"; error: Error };3940type RequestState = LoadingState | SuccessState | ErrorState;4142function renderUI(state: RequestState): string {43 switch (state.status) {44 case "loading":45 return "Loading...";46 case "success":47 return `Got ${state.data.length} items`; // TS knows data exists!48 case "error":49 return `Error: ${state.error.message}`; // TS knows error exists!50 }51}5253// Nullable types (with strictNullChecks)54function findUser(id: number): User | null {55 // might return null if not found56 return id > 0 ? { id, name: "Alice", email: "", createdAt: new Date() } : null;57}58const user = findUser(1);59// user.name; // ❌ Error: 'user' is possibly 'null'60if (user) {61 user.name; // ✅ After null check, TS narrows to User62}6364// Template literal types (powerful with unions!)65type Color = "red" | "blue" | "green";66type Size = "sm" | "md" | "lg";67type ClassName = `${Color}-${Size}`; // "red-sm" | "red-md" | ... (9 combos!)
🏋️ Practice Exercise
Mini Exercise:
- Create a discriminated union for shapes (Circle, Square, Triangle) and write an area calculator with exhaustive switch
- Create a type that represents all HTTP methods as string literals
- Use intersection types to combine
SerializableandValidatableinterfaces - Create a template literal type for CSS class names like
"text-sm","text-md","text-lg" - Write a function that takes
string | number | booleanand handles each case with type narrowing
⚠️ Common Mistakes
Confusing
|(union — either) with&(intersection — both) — they are opposite operations on types, not the same as logical OR/AND on valuesNot handling all cases in a discriminated union — use
neverin the default case for exhaustiveness checkingThinking intersection of primitives works like union —
string & numberisneverbecause no value can be bothForgetting that
nullandundefinedare separate types in strict mode —string | nullandstring | undefinedare differentUsing union types when a discriminated union would be safer — always prefer tagged unions for state management
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for Union, Intersection & Literal Types