Field guide

TypeScript at scale: the rules we wish every codebase followed.

AuthorAtharva KambleFull Stack Engineer
CategoryTypeScript
Reading time4 min read
PublishedJune 12, 2026
Topics
TypeScript best practices TypeScript at scale strict TypeScript TypeScript architecture runtime validation
Illustration: a clean highway with sturdy guardrails and one missing section marked by a warning cone Atharva Kamble, Full Stack Engineer at Script Lanes

TypeScript can make a large codebase safer, easier to refactor and easier to understand. It can also produce a 14-line generic type nobody wants to touch. The goal is useful constraints, not type-system performance art.

TypeScript is a communication tool

The best TypeScript does more than satisfy the compiler. It tells the next developer what data means, which states are valid and what a function promises to return.

In a large codebase, that clarity matters more than cleverness. A type that needs a conference talk to explain is probably not helping the person debugging checkout at 11:30 p.m.

Turn on strictness early

Strict compiler settings — the ones that force you to handle empty values and untyped data — are easiest to switch on at the start. Adding them later to a large, loose codebase becomes a migration project of its own.

Strictness is not there to slow the team down. It pulls vague code into the open, where someone can see it and fix it.

Use unknown at untrusted boundaries

Data from HTTP calls, webhooks, queues, environment variables and third-party SDKs is not trustworthy just because you wrote an interface for it. Mark anything from outside as unknown — TypeScript’s way of saying “we have not established the shape of this yet”. Then check it while the code is running, before you trust it.

Types vanish the moment the code runs. Your payment provider does not care that your editor believed amount was a number.

Diagram: neat code in an editor window, a red shield in the middle, and a crumpled payload slipping past an open gate on the far side
The compiler cannot validate the internet.

Model valid states instead of adding booleans

A pile of true/false flags creates impossible combinations: isLoading=true, hasError=true, isSuccess=true. A discriminated union — one type that lists the allowed states, each carrying its own label — describes only the states the product can really be in. The impossible combinations become much harder to write by accident.

It pays off most in workflows, screens that wait on data, payment states and background jobs.

Diagram: a tangle of interlinked toggle switches beside a clean four-step chain of states
Model states that can actually happen.

Do not export database shapes everywhere

Database records are storage decisions. API payloads are product contracts. Domain models carry business meaning. The three often overlap, but treating them as one type ties together layers that should be free to change on their own.

Convert between them on purpose, at the boundaries that matter. A little repetition is cheaper than turning a column that is allowed to be empty into a promise your public API has to keep.

Types disappear at runtime. Your payment provider did not read your interface.

Avoid the two extremes: any and wizardry

At one extreme, any switches the checks off and quietly removes the guarantees the team thought it had. At the other, people build types so general that simple code becomes hard to read.

Prefer boring, explicit types. Use generics — types with a placeholder filled in later — only when they remove real duplication. If the type is harder to understand than the function, reconsider.

Diagram: a block riddled with holes on one side, a dense tangle of knots on the other, and a clear panel marked in red between them
Avoid both ends.

Keep types close to ownership

Global ‘types.ts’ files tend to become junk drawers. Keep domain types with the domain and component props with the component. Move a type into a shared package only when several parts of the system genuinely share ownership of it.

A type’s location should answer: who is allowed to change this?

TypeScript 6.0 is a reminder to keep configuration current

TypeScript 6.0, released in March 2026, changed several defaults and deprecated a set of old compiler options to clear the path for TypeScript 7.0. The lesson is bigger than one release. Compiler settings are part of the codebase, so review them as deliberately as you review the code.

Do not let tsconfig become an archaeological site full of options nobody remembers choosing.

The rule behind all the rules

Use TypeScript to make important mistakes harder and important ideas clearer. If a rule does neither, it may be ceremony rather than safety.

Found this useful? Build with us.

Tell us what you have in mind. Within 48 hours you'll hear back with an honest plan, clear pricing, and friendly, straight answers.

Start a projectStart a project