5 TypeScript Patterns That Make Code Reviews Painless
Discriminated unions, branded types, exhaustiveness checks, and two more patterns that catch bugs before any reviewer sees them.
The best code review is the one the type system already did. A well-typed TypeScript codebase makes most reviewer feedback unnecessary because the bug never compiles in the first place. Here are the five patterns that have the highest payoff for review quality.
1. Discriminated unions for state
Instead of { loading: boolean; data: T | null; error: string | null } — which can be in invalid states like "loading and error set at the same time" — use a discriminated union:
type Status =
| { kind: "idle" }
| { kind: "loading" }
| { kind: "success"; data: User }
| { kind: "error"; message: string };The reviewer never has to ask "what if data is null but error is also null?" because that state is now unrepresentable.
2. Branded types for IDs
A function that takes userId: string happily accepts a postId too. Brand the type:
type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };Now getUser(postId) is a compile error. Reviewer time saved: enormous.
3. Exhaustiveness checks on switch
Add _exhaustiveCheck to every switch on a union:
switch (status.kind) {
case "idle": return ...
case "loading": return ...
case "success": return ...
case "error": return ...
default:
const _exhaustive: never = status;
return _exhaustive;
}When you add a new state to the union, the compiler tells you every switch you need to update. A reviewer would have caught maybe 60% of those manually.
4. readonly on inputs
Function parameters of type readonly T[] instead ofT[] mean the function cannot mutate the argument. Reviewers no longer need to track "does this function mutate the list I passed in?" — the type answers it.
5. Schema validation at the boundary
Use zod or valibot at every boundary where untrusted data enters: API routes, form submissions, third-party webhooks. The schema is the type, so once it parses, your code is free to trust the shape. Reviewer questions like "what if the API returns null here?" vanish.
The compounding effect
Each of these on its own is a small win. Combined, they take a codebase from "reviewer reads every line for sanity" to "reviewer reads the actual logic." That's the difference between a 30-minute review and a 5-minute one.
When you paste TypeScript into TrashMyCode the AI flags missing exhaustiveness, mutable inputs, and unguarded boundaries automatically. Pair it with these patterns and most code-review comments disappear before they get written.
Want to apply this to your code?
Paste any code into TrashMyCode and get a brutally honest AI review in 30 seconds. Free, no credit card.
Roast my code