Keep strict mode on
Never turn off strict checks for the whole project just to “quickly fix” some errors.
Best practices in TypeScript help you write code that is easy to maintain, easy to grow, and less likely to break. In this section, we will look at patterns that are used in real production projects, good habits for structuring your code, and answers you can give in interviews when asked about working with large codebases. By the end of this section, you will understand how to apply these best practices in your own TypeScript projects.
type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; message: string };Why this pattern is strong:
status fieldtype 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 stops silent bugs from happening when someone adds a new action later but forgets to handle it in the switch statement, TypeScript will warn you about it.
Instead of using plain, generic names like string everywhere, it is better to create clear, named type aliases for your domain (the real-world things your app deals with).
type UserId = string;type Email = string;
interface User { id: UserId; email: Email;}Even though UserId and Email are still just strings underneath, naming them this way makes your code easier to read and shows clearly what each value actually means.
any, Prefer Narrow Contractsfunction parseJson(input: string): unknown { return JSON.parse(input);}After this, you should check the shape of the data before actually using it, instead of just trusting it blindly. Using unknown here is safer than using any, because unknown forces you to check the type before you can use it.
Keep strict mode on
Never turn off strict checks for the whole project just to “quickly fix” some errors.
Prefer explicit API contracts
Types for requests, responses, and shared models should be clear, and kept in one central place.
Use reusable type utilities
Avoid copying and pasting similar type definitions across many files, reuse them instead.
Write readable types
A clever, short, one-line type is usually less valuable than a type that is clear and easy to maintain.
When you are asked “How do you design TypeScript for scale?”, you can answer using this structure:
tsconfig setup and good linting standards.This diagram shows the overall flow: you start with your domain types, pass them through your service layer, then your UI or API handlers use them, the state is tracked using discriminated unions, every case is handled fully, and over time this all makes it safer to change and refactor your code.