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.