Keep strict mode on
Never disable strict checks globally to “quickly fix” errors.
type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; message: string };Why this pattern is strong:
statustype 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.
any, Prefer Narrow Contractsfunction 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:
tsconfig and linting standards.