Patterns include exhaustive switch checks, branded types for domain concepts, readonly for immutability, and discriminated unions for state.
TypeScript offers patterns beyond basic typing that catch entire categories of bugs.
Exhaustive checking ensures you handle all cases. In a switch on a union type, add a default case that assigns to never: default: const _exhaustive: never = status; return _exhaustive. If you add a new union member but forget to handle it, TypeScript errors at compile time.
Branded types add semantic meaning to primitive types. type UserId = string & { readonly brand: unique symbol }. Now you can't accidentally pass a ProductId where a UserId is expected, even though both are strings. Create them with constructor functions that validate the input.
Discriminated unions model states cleanly: type State = { status: 'loading' } | { status: 'success', data: Data } | { status: 'error', error: Error }. TypeScript narrows the type based on the status field, ensuring you only access data when status is 'success'.
Use readonly for data that shouldn't change: readonly arrays, Readonly<T> for objects, as const for literal types. This prevents accidental mutations and documents intent.
Template literal types create precise string types: type Route = `/users/${string}`. These catch typos and enable autocomplete for string-based APIs. Combined with mapped types, they're powerful for type-safe configuration.
Patterns include exhaustive switch checks, branded types for domain concepts, readonly for immutability, and discriminated unions for state.
Join our network of elite AI-native engineers.