All posts
5 TypeScript Patterns I Swear By
TypeScript
JavaScript
Best Practices

5 TypeScript Patterns I Swear By

December 5, 20246 min read

Discriminated unions, const assertions, template literal types and more — the TypeScript features that made me stop writing bugs.


After two years of full-time TypeScript I've settled on a handful of patterns that consistently eliminate entire categories of runtime bugs. Here are the five I reach for most often.

1. Discriminated unions over optional fields

ts
// ❌ optional fields — easy to forget to check
type Result = { data?: User; error?: string };

// ✅ discriminated union — TypeScript enforces the check
type Result =
  | { ok: true;  data: User }
  | { ok: false; error: string };

2. const assertions for literal inference

ts
const ROUTES = ["home", "about", "blog"] as const;
type Route = (typeof ROUTES)[number]; // "home" | "about" | "blog"

3. Template literal types for string APIs

ts
type EventName<T extends string> = `on${Capitalize<T>}`;
type ButtonEvents = EventName<"click" | "focus">; // "onClick" | "onFocus"

4. satisfies for validated literals

The satisfies operator validates a value against a type but keeps the narrowest inferred type — best of both worlds.

ts
type Config = Record<string, string | number>;
const config = {
  port: 3000,
  host: "localhost",
} satisfies Config;
// config.port is still inferred as 3000, not number

5. Branded types for primitive safety

ts
type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };

function getPost(postId: PostId) { /* ... */ }
// getPost(userId) → compile error ✓