Union Types in TypeScript
Union Types in TypeScript
Section titled “Union Types in TypeScript”What are Union Types?
Section titled “What are Union Types?”A union type describes a value that can be one of several types. It’s created using the pipe (|) operator and is one of TypeScript’s most powerful features for modeling flexible data.
Analogy: A union type is like a parking spot that can hold either a car OR a motorcycle OR a bicycle — you know it’s one of those, but you need to check which one before driving away.
flowchart TB subgraph Union[Union Type A | B | C] direction LR A[Type A<br/>string] --- B[Type B<br/>number] --- C[Type C<br/>boolean] end
subgraph Intersection[Intersection Type A & B] direction LR AB[Has ALL properties<br/>of A AND B] end
Union -->|Value can be ONE of| Example1["id: string | number<br/>-> 'abc' OR 42"] Intersection -->|Value must have ALL| Example2["Admin = User & Permissions<br/>-> has name, email, role, permissions"]
style Union fill:#7c3aed,color:#fff style Intersection fill:#3b82f6,color:#fff style A fill:#f59e0b,color:#fff style B fill:#f59e0b,color:#fff style C fill:#f59e0b,color:#fff style AB fill:#059669,color:#fff style Example1 fill:#7c3aed,color:#fff style Example2 fill:#3b82f6,color:#fffVenn diagram thinking: A union (
|) is like the OR area — the value can be in circle A OR circle B. An intersection (&) is like the AND area — the value must be in both circles at once.
Basic Union Syntax
Section titled “Basic Union Syntax”// A variable that can be a string OR a numberlet id: string | number;id = "abc-123"; // OKid = 42; // OK// id = true; // ❌ Error: Type 'boolean' is not assignable
// Function parameter with union typefunction formatInput(input: string | number): string { return `Input: ${input}`;}
formatInput("hello"); // OKformatInput(42); // OK// formatInput(true); // ❌ ErrorType Narrowing with Unions
Section titled “Type Narrowing with Unions”To use a union type safely, you need to narrow it to a specific type:
function processValue(value: string | number) { // typeof narrowing if (typeof value === "string") { // Here, value is string return value.toUpperCase(); } // Here, value is number return value.toFixed(2);}
// Truthiness narrowingfunction getLength(value: string | null): number { if (value) { return value.length; // value is string here } return 0; // value is null here}
// Equality narrowingfunction compare(a: string | number, b: string | boolean) { if (a === b) { // Both a and b are string here (the only overlapping type) console.log(a.toUpperCase()); }}Union Types with Literal Types
Section titled “Union Types with Literal Types”Literal types create unions of specific values:
type Direction = "left" | "right" | "up" | "down";type Status = "idle" | "loading" | "success" | "error";type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function move(direction: Direction): void { console.log(`Moving ${direction}`);}
move("left"); // OK// move("back"); // ❌ Error: Type '"back"' is not assignable
function rollDice(): DiceRoll { return (Math.floor(Math.random() * 6) + 1) as DiceRoll;}Discriminated Unions
Section titled “Discriminated Unions”A discriminated union uses a common property (the discriminant) to distinguish between variants:
// Each variant has a 'kind' property that acts as the discriminanttype Shape = | { kind: "circle"; radius: number } | { kind: "rectangle"; width: number; height: number } | { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number { // TypeScript narrows based on the discriminant 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; }}Union Types with Arrays
Section titled “Union Types with Arrays”// Array of strings OR numbers (not mixed)let arr: string[] | number[];arr = ["a", "b", "c"]; // OKarr = [1, 2, 3]; // OK// arr = ["a", 1]; // ❌ Error
// Array with mixed typeslet mixed: (string | number)[];mixed = ["a", 1, "b", 2]; // OK
// Tuple-like arrayslet pair: [string, number];pair = ["age", 25]; // OK// pair = [25, "age"]; // ❌ Error: wrong orderNullable Types
Section titled “Nullable Types”null and undefined are often part of unions:
type MaybeString = string | null;type OptionalNumber = number | undefined;
function findUser(id: string): User | null { const user = database.find(id); return user || null;}
// With strictNullChecks, optional parameters are unionsfunction greet(name?: string): string { // name is string | undefined return `Hello, ${name ?? "Guest"}`;}Real Project Example
Section titled “Real Project Example”// API response statestype ApiState<T> = | { status: "idle" } | { status: "loading" } | { status: "success"; data: T } | { status: "error"; error: string };
// React component usagefunction UserProfile() { const [state, setState] = useState<ApiState<User>>({ status: "idle" });
if (state.status === "loading") return <Spinner />; if (state.status === "error") return <Error message={state.error} />; if (state.status === "success") return <Profile user={state.data} />; return <Button onClick={fetchUser}>Load Profile</Button>;}Common Mistakes
Section titled “Common Mistakes”| Mistake | Why It’s Wrong | Fix |
|---|---|---|
| Forgetting to narrow union types | Can’t access type-specific properties | Use typeof, instanceof, or discriminant checks |
| Using ` | ` with too many types | Hard to maintain |
| Not handling all union members | Runtime errors from unhandled cases | Use exhaustive checks with never |
| Mixing up ` | ` in arrays vs union of arrays | (string|number)[] vs string[]|number[] |
Interview Questions
Section titled “Interview Questions”Easy: What is a union type in TypeScript? How do you create one?
Medium: Explain discriminated unions with an example. Why are they useful?
Hard: How does TypeScript narrow types in a discriminated union within a switch statement? What happens if you add a new variant?