All blogs

TypeScript Types That Catch Real Bugs

Forget type gymnastics. These five everyday patterns stop the bugs that actually reach production.

1 min read

TypeScript is at its best when it quietly stops a bug before you even run the code. You do not need advanced generics for that. A few simple habits catch most real-world mistakes.

1. Model states, not flags

A request is never loading and failed at the same time, so do not let your types allow it.

type Request =
  | { status: 'loading' }
  | { status: 'error', message: string }
  | { status: 'success', data: User[] }

Now every switch on status knows exactly which fields exist.

2. Make impossible values impossible

Use string unions instead of plain strings for anything with a fixed set of options, like sizes, roles or themes.

type Theme = 'light' | 'dark'

3. Prefer unknown over any

any switches the type checker off. unknown forces you to check what you received before using it, which is exactly what you want for API responses and user input.

4. Let the compiler check every case

function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${value}`)
}

Call it in the default branch of a switch, and adding a new case anywhere becomes a compile error until you handle it.

5. Type the edges of your app

Most bugs enter where data crosses a boundary: forms, API calls, local storage. Validate there, give the result a precise type, and the rest of your code can trust it.

“Programs must be written for people to read, and only incidentally for machines to execute.”
— Harold Abelson
Made with Modulify