TypeScript Patterns That Saved Our Codebase
TypeScript’s value isn’t just autocomplete. It’s making invalid states unrepresentable. Here are patterns we reach for constantly.
Discriminated unions for state machines
Stop modeling state with booleans. They create impossible combinations.
// Before: boolean soup
interface Request {
isLoading: boolean;
isError: boolean;
data?: User;
error?: Error;
}
// What if isLoading AND isError are both true?
// After: impossible states are impossible
type Request =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error };
The compiler now forces exhaustive handling. No more “undefined is not an object.”
Branded types for domain safety
A userId and an orderId are both strings. They should never be interchangeable.
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
const createUserId = (id: string): UserId => id as UserId;
function getOrder(id: OrderId) { /* ... */ }
const userId = createUserId('u_123');
getOrder(userId); // Compile error! Type 'UserId' is not assignable to 'OrderId'
Zero runtime cost. Infinite bug prevention.
Result types over exceptions
Exceptions are invisible in function signatures. Make errors explicit.
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function parseConfig(raw: string): Result<Config, ParseError> {
try {
return { ok: true, value: JSON.parse(raw) };
} catch (e) {
return { ok: false, error: new ParseError(e.message) };
}
}
// Caller MUST handle both cases
const result = parseConfig(input);
if (!result.ok) {
console.error(result.error);
return;
}
// result.value is now Config, guaranteed
Const assertions for literal types
When you need exact values, not just their types:
const HTTP_STATUS = {
OK: 200,
NOT_FOUND: 404,
SERVER_ERROR: 500,
} as const;
type StatusCode = typeof HTTP_STATUS[keyof typeof HTTP_STATUS];
// Type is 200 | 404 | 500, not number
The theme across all these patterns: push validation to compile time. Every bug the compiler catches is a bug that never reaches production.