Five TypeScript Patterns I Use Every Day
typescript
TypeScript is most useful when it catches real bugs, not when it forces you to write elaborate types. Here are five patterns I reach for regularly.
1. Discriminated unions for state
Instead of optional fields that can be in inconsistent combinations, model state explicitly:
type LoadState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; message: string };
The compiler narrows the type correctly in each branch of a switch statement.
2. satisfies for config objects
satisfies lets you validate an object against a type without widening it:
const config = {
port: 3000,
host: "localhost",
} satisfies Record<string, string | number>;
// config.port is inferred as `number`, not `string | number`
3. Template literal types for string patterns
type EventName = `on${Capitalize<string>}`;
// Valid: "onClick", "onChange"
// Invalid: "click", "change"
4. infer in conditional types
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
5. Const assertions for literal types
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
This is cleaner than maintaining a separate type and array that can drift out of sync.