Home·Blog·Web Dev
Web Dev

TypeScript Patterns That Will Make Your Codebase Bulletproof.

Beyond basic types — discriminated unions, template literal types, satisfies operator, and the patterns that separate good TypeScript from great TypeScript.

10 min readMar 2025Ababil.sec

Most TypeScript codebases use only a fraction of the type system's capabilities. Basic interfaces and enums are a start — but the patterns that truly eliminate runtime errors require going deeper.

Discriminated Unions

Discriminated unions model states that are mutually exclusive. They eliminate impossible states and enable exhaustive checking:

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

function render(result: Result<User>) {
  switch (result.status) {
    case 'success': return result.data.name; // TS knows data exists
    case 'error':   return result.message;
    case 'loading': return 'Loading...';
  }
}

The satisfies Operator

Introduced in TypeScript 4.9, satisfies validates that a value conforms to a type while preserving the inferred type for downstream use:

const config = {
  theme: 'dark',
  lang: 'en',
} satisfies Record<string, string>;

// config.theme is inferred as 'dark', not string
config.theme.toUpperCase(); // works!

Template Literal Types

Template literal types create string types that follow a pattern — extremely useful for event systems, CSS-in-JS, and API route typing:

type EventName = 'click' | 'focus' | 'blur';
type Handler = 'on${Capitalize<EventName>}';
// Handler = 'onClick' | 'onFocus' | 'onBlur'

Branded Types

Branded types prevent mixing structurally identical but semantically different values — like user IDs and product IDs both typed as strings:

type UserId = string & { readonly brand: unique symbol };
type ProductId = string & { readonly brand: unique symbol };

function getUser(id: UserId) { ... }
const productId = 'abc' as ProductId;
getUser(productId); // TS Error! Type mismatch.

Strict Mode Is Not Optional

Enable strict: true in tsconfig.json from day one. It enables strictNullChecks, noImplicitAny, and several other checks that catch entire categories of bugs at compile time. Retrofitting strict mode onto an existing codebase is painful — starting strict is free.

Zod for Runtime Validation

TypeScript types are erased at runtime. Zod bridges the gap by providing runtime schema validation that infers TypeScript types — eliminating duplication between your type definitions and validation logic:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  age: z.number().min(18),
});

type User = z.infer<typeof UserSchema>;

Conclusion

The TypeScript type system is one of the most sophisticated in any mainstream language. The patterns here — discriminated unions, satisfies, branded types, and Zod — eliminate entire categories of bugs that unit tests often miss. Invest time learning them; the payoff is a codebase that fails at compile time rather than production.

Ready to Secure Your
Project?

Get a professional security audit or start a project with us today.

Start a Project
Related Articles