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.

Code editor open on a monitor in a developer workspace

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

  1. TypeScript Handbook — Narrowing (discriminated unions)
  2. TypeScript Handbook — Types from Types (const assertions, mapped types, template literals)
  3. TypeScript Handbook — Declaration Files: Do's and Don'ts (interface vs type guidance)
  4. Microsoft DevBlogs — Announcing TypeScript 4.1 (template literal types)