← Back to principles

UI

Type Safety as Leverage

Statement

Strong typing catches errors at build time and serves as living documentation. TypeScript should guide correct usage and catch real bugs without drowning developers in complex generics. Type safety must extend end to end across the full stack — and where types or API specs don't exist, create them.

What this means in practice

Types are not bureaucracy — they are a tool that prevents entire categories of bugs, documents intent, and enables confident refactoring. Type safety is pursued across every boundary: front end, back end, API contracts, and third-party integrations.

  • TypeScript is used with strict mode enabled as the default across all projects.

  • Types are practical and readable — they describe real data shapes and guide correct usage without resorting to deeply nested generics or type gymnastics.

  • End-to-end type safety spans the full stack: from database schema through API layer to UI components. A change to a data shape should surface type errors at every point it is consumed.

  • API contracts are typed and shared between client and server — generated from OpenAPI specs, GraphQL schemas, or equivalent contract-first approaches.

  • Where a third-party API or internal service does not provide types or a formal spec, the consuming team creates and maintains types for it rather than falling back to any or untyped objects.

  • Missing API specs are treated as a gap to be closed, not accepted as the status quo. If a spec doesn't exist, write one. If it's incomplete, extend it.

  • any, unknown casts, and @ts-ignore are used sparingly and always flagged in code review with a justification.

Why this matters

Untyped boundaries are where bugs hide and confidence breaks down.

  • Runtime errors caused by mismatched data shapes between client and server are among the most common and most preventable defects.

  • Without end-to-end types, a backend change can silently break the frontend — and nobody finds out until a user reports it.

  • Types serve as living documentation that stays in sync with the code, unlike comments or external docs that drift over time.

  • Strong typing enables IDE tooling (autocompletion, inline errors, safe refactoring) that dramatically improves developer velocity.

  • Accepting untyped third-party APIs without creating types means every consumer independently guesses the data shape, leading to inconsistency and subtle bugs.

  • Teams that invest in type safety spend less time debugging data mismatches and more time building features.

Practices that meet this principle

  • Enable TypeScript strict mode (strict: true) on all projects and treat type errors as build failures.

  • Generate client types from API specs (OpenAPI, GraphQL codegen, tRPC, or equivalent) so contracts are shared, not duplicated.

  • Where an API does not have a formal spec, create one — even if it starts as a hand-written TypeScript interface or a lightweight OpenAPI definition.

  • Challenge third-party integrations that lack type definitions: write and maintain types locally, contribute them upstream where possible, or use tools like @types/* packages.

  • Avoid any — prefer unknown with type guards where the shape genuinely isn't known at compile time.

  • Flag every @ts-ignore and as any in code review; require a comment explaining why it's necessary and a plan to remove it.

  • Keep types simple and close to the data — prefer interfaces and type aliases that mirror real API responses over elaborate generic abstractions.

  • Use runtime validation (Zod, io-ts, or similar) at system boundaries to ensure external data matches expected types.

  • Include type coverage in CI checks and track it as a quality metric.

Validation

A project meets this principle when:

  • TypeScript strict mode is enabled and enforced across all repositories.

  • API contracts between client and server are typed and ideally generated from a single source of truth.

  • Third-party APIs consumed by the team have locally maintained type definitions, not untyped access.

  • Usage of any, type assertions, and @ts-ignore is minimal, justified, and visible in code review.

  • A change to a data shape in the API layer surfaces compile-time errors in all consuming code.

  • Runtime validation is applied at external boundaries (API responses, webhook payloads, third-party data).

  • Developers can refactor with confidence, relying on the type system to catch breakage across the stack.

  • Missing or incomplete API specs are actively identified and addressed, not worked around.

Potential blockers

  • Legacy JavaScript codebases that have not yet been migrated to TypeScript.

  • Third-party services with undocumented or frequently changing APIs.

  • Internal APIs without formal specs, requiring upfront effort to define contracts.

  • Team members unfamiliar with TypeScript's type system, leading to over-reliance on any or overly complex generics.

  • Pressure to ship quickly, tempting teams to skip type definitions for "just this once".