TypeScript Best Practices
tsconfig: enable strict
- •
strict: trueintsconfig.json(or in base config). EnablesnoImplicitAny,strictNullChecks,strictFunctionTypes, and related checks. Catch errors at compile time instead of runtime. - •
noImplicitAny: No implicitany; require explicit types where inference would beany(e.g. parameters). Useunknownwhen the type is truly unknown, then narrow. - •
strictNullChecks:nullandundefinedare distinct; types must include| nullor| undefinedwhen values can be nullish. Forces handling before property access. - •Optional:
noUnusedLocals,noUnusedParameters,strictPropertyInitialization,exactOptionalPropertyTypesfor stricter code.
Prefer inference; annotate when needed
- •Let TypeScript infer return types and variable types when obvious. Reduces noise and keeps types in sync with implementation.
- •Annotate: function parameters (if inference would be
any), public API boundaries, and when it improves readability or documents intent. - •Avoid redundant annotations (e.g.
const x: number = 1when inference is clear).
Avoid any; use unknown and narrow
- •Do not use
anyas an escape hatch; it disables type checking. Use only when interfacing with untyped JS or when explicitly opting out (and document why). - •
unknown: Use for values whose type is not known. Require type narrowing (typeof, type guards, assertions) before use. - •Type assertions (
as T): Use sparingly; they bypass checks. Prefer type guards or refactors so the type is inferred. If you assert, ensure the value really isT.
Interfaces and types
- •Interfaces: For object shapes and contracts; can be extended and merged. Prefer for public API and React props when the project convention is interface.
- •Type aliases: For unions, intersections, mapped types, and aliasing other types. Use for unions, tuples, and complex compositions.
- •Be consistent within the project; both work with utility types.
Utility types
Use built-in utility types instead of duplicating shapes:
- •
Partial<T>— All properties optional (e.g. update payloads). - •
Required<T>— All properties required. - •
Readonly<T>— All properties readonly. - •
Pick<T, K>— Subset of properties (allow-list). - •
Omit<T, K>— Type without listed properties (block-list). - •
Record<K, V>— Object type with keysKand value typeV. - •
NonNullable<T>— Excludenullandundefined. - •
ReturnType<F>— Return type of function typeF. - •
Parameters<F>— Tuple of parameter types ofF.
Prefer Pick/Omit over copying and pasting properties.
Null and undefined
- •With
strictNullChecks: handlenull/undefinedexplicitly (optional chaining?., nullish coalescing??, guards, or!only when you have proven non-null). - •Prefer
undefinedfor “missing” in optional properties; use| nullwhen the value can be explicitly null (e.g. APIs). - •Avoid
strictNullChecks: falsein new code; it hides null/undefined bugs.
Functions
- •Type parameters and return type when they are part of the public API or when inference would be
any. - •Prefer
(x: string) => numberoverFunctionor untyped callbacks. - •Use
voidfor return type when the function returns nothing meaningful; avoid returningundefinedexplicitly unless required. - •Async: return
Promise<T>; inference usually gives this fromasyncfunctions.
Generics
- •Use generics for reusable types and functions that work with multiple types; avoid
anyin generic signatures. - •Add constraints (
extends) when the generic must have certain properties (e.g.T extends { id: string }). - •Prefer a single letter or short name for type parameters (
T,K,V) unless a longer name clarifies.
Discriminated unions and narrowing
- •Use discriminated unions (common property, e.g.
type: "success" | "error") for state or result types; narrow withswitchorifon the discriminant. - •Use type guards (
x is T) for custom narrowing; keep guards small and reliable. - •Prefer
satisfieswhen you need to validate a value’s shape without widening to a looser type.
React / frontend (when applicable)
- •Type component props with an interface or type; avoid inline object types for complex props.
- •Use
React.FConly if the project convention does; otherwisefunction Component(props: Props): JSX.Elementor inferred return. - •Type hooks (useState, custom hooks) so initial value and setter are correctly inferred; use generics for useState when the value can be null initially.
- •Type event handlers:
React.ChangeEvent<HTMLInputElement>, etc., instead ofany.
Backend / API (when applicable)
- •Type request/response bodies and DTOs with interfaces or types; align with OpenAPI/spec if present.
- •Type route handlers and middleware (e.g.
Request,Response, extended types forreq.user). - •Use
unknownfor parsed JSON or external input; validate and narrow (e.g. with Zod or a type guard) before use.
Checklist
- •
strict: true(and desired strict options) in tsconfig. - • No implicit
any; useunknownand narrow when needed. - • Null/undefined handled explicitly where
strictNullChecksis on. - • Utility types used instead of duplicated shapes.
- • Public APIs and boundaries have clear types.
- • No unnecessary type assertions; prefer inference or type guards.
Reference
- •tsconfig strict options and utility types list: reference.md
- •Official docs: typescriptlang.org/docs