TypeScript Patterns I Use Every Day
A handful of TypeScript tricks that have made my code more expressive and easier to maintain.
TypeScriptJavaScriptBest Practices
After years of writing TypeScript across many projects, certain patterns have become muscle memory. Here are the ones that consistently make a difference.
Discriminated Unions for State
Instead of a maze of booleans, model your state as a union:
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: string };
The compiler will force you to handle every case.
satisfies for Config Objects
satisfies lets you validate a value against a type without widening it:
const routes = {
home: "/",
about: "/about",
} satisfies Record<string, string>;
// routes.home is still typed as "/" not string
Template Literal Types
Great for building typed event systems or API route definitions:
type Method = "GET" | "POST" | "DELETE";
type Route = "/users" | "/posts";
type Endpoint = `${Method} ${Route}`;
// "GET /users" | "GET /posts" | "POST /users" | ...
infer in Conditional Types
Extract inner types without a utility library:
type Awaited<T> = T extends Promise<infer U> ? U : T;
type ElementType<T> = T extends (infer E)[] ? E : never;
These patterns pay off the moment your codebase grows beyond a single person.