TypeScript Best Practices for Clean, Maintainable Code

December 8, 2024

TypeScript Best Practices for Clean, Maintainable Code

TypeScript shines when it helps you model reality — not when it forces you to fight types all day. A few small defaults can make a codebase feel calmer, safer, and easier to refactor.

TypeScript code on screen

Practical rules of thumb

  • Turn on strictness and fix the sharp edges early.
  • Prefer readable types over clever types.
  • Use unions for “one of these”, interfaces for “shape of this”.
  • Avoid any as a shortcut; it becomes future debt fast.

One pattern worth memorizing

type Result<T> = | { ok: true; value: T } | { ok: false; error: string }; export function parseNumber(input: string): Result<number> { const n = Number(input); return Number.isFinite(n) ? { ok: true, value: n } : { ok: false, error: "Not a number" }; }

Narrow before you use it

A Result<T> is only useful if TypeScript can narrow it for you. Check the ok field and the compiler rules out the other branch automatically — no casts, no !:

const result = parseNumber(input); if (!result.ok) { console.error(result.error); // `error` is known to exist here return; } console.log(result.value * 2); // `value` is known to exist here

This is the payoff of modeling with discriminated unions: the type checker enforces that you've handled both the success and failure paths before touching the data.

Wrap-up

The best TypeScript code reads like good documentation: clear names, predictable shapes, and errors that point you to the fix.

GitHub
LinkedIn