Skip to content

Best Practices

type ApiResponse<T> =
| { status: "success"; data: T }
| { status: "error"; message: string };

Why this pattern is strong:

  • explicit success and error shapes
  • easy narrowing by status
  • safer API handling in UI and services

type Action =
| { type: "increment"; amount: number }
| { type: "decrement"; amount: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + action.amount;
case "decrement":
return state - action.amount;
default: {
const _never: never = action;
return _never;
}
}
}

This prevents silent bugs when new actions are introduced.


Instead of generic names like string, create clear domain aliases.

type UserId = string;
type Email = string;
interface User {
id: UserId;
email: Email;
}

This improves readability and intent.


Pattern 4: Avoid any, Prefer Narrow Contracts

Section titled “Pattern 4: Avoid any, Prefer Narrow Contracts”
function parseJson(input: string): unknown {
return JSON.parse(input);
}

Then validate shape before use.


Keep strict mode on

Never disable strict checks globally to “quickly fix” errors.

Prefer explicit API contracts

Types for requests, responses, and shared models should be clear and centralized.

Use reusable type utilities

Avoid copy-pasting similar type definitions across files.

Write readable types

Clever one-line types are less valuable than clear maintainable ones.


When asked “How do you design TypeScript for scale?”, answer in this structure:

  1. Start with strict tsconfig and linting standards.
  2. Model domain objects using interfaces and type aliases.
  3. Use unions and discriminators for state and API flows.
  4. Apply generics for reusable infrastructure.
  5. Use utility, mapped, and conditional types when duplication appears.
  6. Add exhaustiveness checks to prevent unhandled cases.
  7. Use runtime validation for external input.

graph LR A[Domain Model Types] --> B[Service Layer] B --> C[UI or API Handlers] C --> D[Discriminated State] D --> E[Exhaustive Handling] E --> F[Safe Refactoring Over Time]