Fyutrex
Back to BlogEngineering

TypeScript Patterns That Ship: What We Use in Every Production Codebase

After 50+ production TypeScript projects, these are the patterns we've standardised across every codebase at Fyutrex. No academic exercises — just patterns that make real teams faster.

A

Alex Morgan

Lead Engineer

Mar 28, 20268 min read

TypeScript's type system is extraordinarily powerful, but power without discipline leads to codebases that are harder to work with than plain JavaScript. At Fyutrex, we've shipped over 50 production TypeScript projects and have converged on a set of patterns that we use in every single one. These aren't clever type gymnastics — they're practical patterns that make real teams ship faster with fewer bugs.

Branded Types for Domain Safety

The most impactful TypeScript pattern we use is branded types. They prevent entire categories of bugs by making it impossible to accidentally swap values that share the same primitive type.

Branded Types Example

type Brand<T, B extends string> = T & { __brand: B }
type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>

function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }

// This won't compile — you can't pass an OrderId where a UserId is expected
const orderId = 'abc' as OrderId
getUser(orderId) // ← Type error!

The Result Pattern Over Exceptions

We never throw exceptions for expected error cases. Instead, we use a Result pattern that forces callers to handle both success and failure paths. This eliminates an entire class of unhandled error bugs and makes error handling explicit in every function signature.

The key insight is that exceptions should be reserved for truly exceptional situations — programmer errors, infrastructure failures, things that can't be meaningfully handled by the caller. Business logic errors (validation failures, not-found, permission denied) should be return values.

Pro Tip

Every project starts with these compiler options: strict: true, noUncheckedIndexedAccess: true, exactOptionalPropertyTypes: true, and noImplicitOverride: true. The first week with these settings is painful. Every week after that, they prevent bugs that would have taken hours to track down.

Discriminated Unions for State Machines

Every piece of state in our applications that can be in multiple states is modelled as a discriminated union. This pattern eliminates impossible states at the type level and makes exhaustive checking trivial.

We use this for API response states, form states, auth states, and any workflow that has distinct phases. The TypeScript compiler becomes your state machine verifier — if you forget to handle a state, it tells you at build time, not at runtime in production.

Zod at Every Boundary

We validate data at every system boundary using Zod schemas that double as TypeScript types. API responses, form inputs, environment variables, configuration files — all validated at runtime with schemas that generate their TypeScript types automatically. This 'parse, don't validate' approach means we trust our types throughout the application because we've verified the data at the edges.


Conclusion

These patterns aren't revolutionary individually. Their power comes from consistent application across an entire codebase. When every team member uses the same patterns, code reviews are faster, onboarding is smoother, and bugs are caught before they reach production.


A

Written by

Alex Morgan

Lead Engineer at Fyutrex

Alex is a senior full-stack engineer at Fyutrex with deep expertise in Next.js, TypeScript, and cloud-native architecture.

More from Alex

Want help building this?

Let's talk. We'll help you turn these ideas into production software.

Start a Conversation
Free 30-min consultationNo commitment24h response