TypeScript Patterns for Better DX
TypeScript can feel like overhead until it saves you from a production bug. These patterns maximise the benefits while keeping your code readable and concise.
TypeScript's value compounds over time. Early in a project it can feel like friction; six months in, when it catches a breaking API change before it reaches users, it pays for itself.
Discriminated unions for state
Model loading states explicitly rather than as a bag of optional fields.
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
## Const assertions for immutable data
Mark configuration objects and lookup tables as `as const` to get the narrowest possible types.
const ROLES = ['admin', 'editor', 'viewer'] as const
type Role = (typeof ROLES)[number] // 'admin' | 'editor' | 'viewer'Prefer interfaces for public APIs
Use interface for props and public contracts — they produce cleaner error messages and
support declaration merging. Use type for unions, intersections, and mapped types.
Template literal types
TypeScript 4.1+ template literal types unlock expressive, type-safe string manipulation.
type EventName = `on${Capitalize<string>}`References
- TypeScript Handbook — Narrowing (discriminated unions)
- TypeScript Handbook — Types from Types (const assertions, mapped types, template literals)
- TypeScript Handbook — Declaration Files: Do's and Don'ts (interface vs type guidance)
- Microsoft DevBlogs — Announcing TypeScript 4.1 (template literal types)