TypeScript Patterns I Actually Use
Not the advanced wizardry — just the practical patterns that show up in real codebases and make the type system work for you.
There's a gap between "TypeScript exists" and "TypeScript is making my codebase meaningfully safer." These are the patterns that close that gap in day-to-day work.
Discriminated Unions for State
Instead of a mess of optional fields, model mutually exclusive states explicitly:
type RequestState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: string };
Now exhaustive switch statements work and TypeScript narrows data and error to the right variants automatically.
satisfies for Config Objects
satisfies validates against a type without widening — you keep the literal types:
const routes = {
home: "/",
blog: "/blog",
contact: "/contact",
} satisfies Record<string, string>;
// routes.home is typed as "/" not string
This is great for config objects where you want both validation and autocompletion on the literal values.
Awaited<ReturnType<...>>
When you need the resolved return type of an async function without duplicating a type definition:
async function getUser(id: string) {
return db.user.findUnique({ where: { id } });
}
type User = Awaited<ReturnType<typeof getUser>>;
Branded Types for IDs
Plain string types for IDs are too permissive. Brand them to prevent mixing up UserId and PostId:
type UserId = string & { readonly _brand: "UserId" };
type PostId = string & { readonly _brand: "PostId" };
function createUserId(id: string): UserId {
return id as UserId;
}
Now passing a PostId where a UserId is expected is a type error.
infer for Extracting Types
Useful when you need to pull a type out of a generic:
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type UnpackArray<T> = T extends Array<infer U> ? U : T;
Closing Thoughts
The goal isn't type gymnastics — it's catching real bugs. Discriminated unions and branded types give you the most safety per line of type code. Use the rest when the problem actually calls for it.