Back to Garden
Article
TypeScript Patterns I Use Daily
Practical TypeScript patterns that make code more maintainable, readable, and type-safe.
Peter
#typescript#patterns#best-practices
TypeScript Patterns I Use Daily
After years of writing TypeScript, these are the patterns I reach for most often.
1. Discriminated Unions
Perfect for state management and API responses:
type LoadingState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function handleState<T>(state: LoadingState<T>) {
switch (state.status) {
case 'idle':
return 'Ready to load';
case 'loading':
return 'Loading...';
case 'success':
return `Got: ${state.data}`;
case 'error':
return `Error: ${state.error.message}`;
}
}2. Type Predicates
For narrowing types in a reusable way:
interface User {
id: string;
name: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value
);
}
// Usage
const maybeUser: unknown = fetchData();
if (isUser(maybeUser)) {
console.log(maybeUser.name); // TypeScript knows it's a User
}3. Const Assertions
For literal types without type annotations:
const ROUTES = {
home: '/',
about: '/about',
work: '/work',
} as const;
type Route = typeof ROUTES[keyof typeof ROUTES];
// Type is '/' | '/about' | '/work'4. Template Literal Types
For string patterns:
type EventName = `on${Capitalize<string>}`;
type CSSUnit = `${number}${'px' | 'em' | 'rem' | '%'}`;
const margin: CSSUnit = '16px'; // ✓
const padding: CSSUnit = '1.5rem'; // ✓5. Branded Types
For type-safe IDs:
type UserId = string & { readonly __brand: 'UserId' };
type PostId = string & { readonly __brand: 'PostId' };
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }
const userId = createUserId('123');
getUser(userId); // ✓
// getPost(userId); // ✗ Type error!Conclusion
These patterns have saved me countless hours of debugging and made my code more self-documenting. The key is knowing when to apply them—not every situation needs advanced types.