Unions, Enums & Discriminated Unions
Union types model "one of several shapes", the pattern at the heart of idiomatic TypeScript. Discriminated unions make them safe to work with.
Union & Intersection Types
A union (A | B) is a value that is either A or B. An intersection (A & B) is a value that is A and B at once (has all properties of both).
// union — one of several types type ID = string | number; let id: ID = 42; id = "a1"; // both allowed // on a union you can only use members COMMON to all types... function len(x: string | string[]) { return x.length; // OK — both have .length // x.toUpperCase(); // Error — arrays don't have it, must narrow first } // intersection — combine into one shape with all props type Named = { name: string }; type Aged = { age: number }; type Person = Named & Aged; // { name: string; age: number }
Literal Types
A literal type is a type whose only value is one specific string, number, or boolean. Unions of literals give you type-safe "enum-like" sets without a runtime enum.
// a set of allowed string values, checked at compile time type Direction = "north" | "south" | "east" | "west"; function move(dir: Direction) { /* ... */ } move("north"); // OK // move("up"); // Error: not assignable to Direction // combine with numbers type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
Discriminated Unions
A discriminated union gives each member a shared literal "tag" field. Switching on that tag lets TypeScript narrow to the exact member, so each branch knows precisely which properties exist. This is the single most powerful TS modelling pattern.
type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number } | { kind: "rect"; w: number; h: number }; function area(s: Shape): number { switch (s.kind) { case "circle": return Math.PI * s.radius ** 2; // s.radius known here case "square": return s.side ** 2; case "rect": return s.w * s.h; default: const _exhaustive: never = s; // compile error if a case is missed return _exhaustive; } }
never assignment in default is an exhaustiveness check: if you later add a new shape and forget to handle it, TypeScript errors here at compile time. This is how TS turns "did I cover every case?" into a guarantee.Enums
An enum is a named set of constants that does exist at runtime (unlike literal unions). Useful when you need the values reflected at runtime, but often a literal union or as const object is preferred.
enum Status { Active, Inactive, Pending } // 0, 1, 2 let s: Status = Status.Active; // string enums — more readable at runtime enum Level { Low = "LOW", High = "HIGH" } // modern alternative: 'as const' object + literal union const Roles = { Admin: "admin", User: "user" } as const; type Role = typeof Roles[keyof typeof Roles]; // "admin" | "user"
as const objects over enums, which produce a runtime object you might not want.