Static Typing Best Practices for 2026

Static Typing Best Practices for 2026

A type checker is a design tool disguised as a linter. Used casually it catches typos; used deliberately it makes illegal states unrepresentable. These are the practices that consistently pay off in application code.

Model states, not shapes

The biggest wins come from union types that enumerate what can actually happen:

type RequestState<T> = { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; message: string };

With this model, data simply does not exist unless status === 'success' — a whole family of “cannot read property of undefined” bugs stops compiling.

Prefer inference at the edges

Annotate public boundaries (exported functions, module APIs) and let inference do the interior work:

export function totalByRegion(orders: Order[]): Map<string, number> {
  const totals = new Map<string, number>();
  for (const order of orders) {
    totals.set(order.region, (totals.get(order.region) ?? 0) + order.amount);
  }
  return totals;
}

Over-annotating locals adds noise without adding safety, and it hides the moments when inference disagrees with your intent — which is exactly the signal you want.

Narrow, don’t assert

Every assertion is a promise the compiler cannot check. Replace them with narrowing:

function isDefined<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined;
}

const names = users.map((u) => u.nickname).filter(isDefined);

A small library of type guards eliminates most of the places assertions used to feel necessary.

Make constants literal

as const turns configuration into checkable facts:

const ROLES = ['viewer', 'editor', 'admin'] as const;
type Role = (typeof ROLES)[number]; // 'viewer' | 'editor' | 'admin'

Now a typo’d role is a compile error everywhere, and the list has exactly one source of truth.

Keep generics honest

Reach for a generic only when two call sites would otherwise force a cast. If a generic parameter appears once in a signature, it is usually a disguised unknown and should be removed. Good generic code reads like a relationship: what comes out depends on what went in.

Practices worth enforcing

  1. Enable the strictest checker settings on day one — retrofitting is 10× the cost.
  2. Ban unchecked assertions in review; require a narrowing function instead.
  3. Type errors at boundaries (API responses, storage) with schema validation, not trust.
  4. Treat any as a loan with interest; track and repay it.

Conclusion

Types earn their keep when they describe behavior: which states exist, what depends on what, which promises are checked. Model unions first, infer the interior, narrow at the edges — and the checker becomes a collaborator instead of a gate.