TypeScript Best Practices
Types are documentation that the compiler checks. Write types that reveal intent, not types that silence the compiler.
Compiler Settings
Always use "strict": true in tsconfig.json. Additionally enable:
"noUncheckedIndexedAccess": true— array and object index access returnsT | undefined."exactOptionalPropertyTypes": true—{ a?: string }means absent, notstring | undefined."noImplicitReturns": trueand"noFallthroughCasesInSwitch": true."moduleResolution": "bundler"(or"node16"/"nodenext") for modern module semantics.
Type Precision
- Prefer
unknownoveranyfor values of unknown shape; narrow with type guards before use. - Use
constassertions (as const) to preserve literal types in arrays and objects. - Model discriminated unions over class hierarchies for state machines and response variants.
- Avoid
!non-null assertions; prefer explicit narrowing or nullish coalescing (??). - Use
satisfiesto validate a value against a type without widening the inferred type. - Prefer
readonlyarrays and properties in function signatures; mutate via explicit copies.
Functions and Generics
- Annotate return types explicitly on all exported functions to prevent accidental widening.
- Constrain generics minimally:
<T extends string>not<T>when you only need string operations. - Prefer function overloads over union parameter types when the return type changes with input shape.
- Use
Parameters<typeof fn>,ReturnType<typeof fn>, andAwaited<T>over duplicating types.
Narrowing and Type Guards
- Use
typeof,instanceof,in, and discriminant property checks — notascasts. - Write user-defined type guards (
value is Type) only when built-in narrowing cannot express the check. - Use exhaustiveness checks:
default: { const _: never = x; throw new Error('unhandled case'); }. - Never use
as unknown as TargetTypeas a shortcut — it hides real type errors.
JavaScript Migration
- Add
"allowJs": trueand"checkJs": true— fix reported errors without renaming files. - Rename files leaf-first: modules with no imports of other JS files first, then work up the dependency graph.
- Generate
.d.tsstubs for third-party modules missing types before adding them to the graph. - Replace
@ts-ignorewith@ts-expect-errorplus a reason comment; delete when fixed. - Enable
strictonly after the codebase is fully.ts— tackle one error category at a time.
Code Review Checklist
- No
anyin new or changed code — useunknown+ narrowing or a named type. - All exported API surfaces have explicit return type annotations.
- Discriminated unions cover all cases with exhaustiveness checks.
-
"strict": truein tsconfig; no newskipLibChecksuppressions. - No
ascasts that skip validation; use type guards instead.