Design Patterns
Patterns are named solutions to recurring design problems. The value is the shared vocabulary — "use a Strategy here" communicates more than describing the implementation.
Pattern Categories (GoF)
When to Reach for What
Patterns I Actually Use
Strategy
Swap algorithms at runtime without changing the caller.
interface SortStrategy {
sort(data: number[]): number[];
}
class QuickSort implements SortStrategy { ... }
class MergeSort implements SortStrategy { ... }
class Sorter {
constructor(private strategy: SortStrategy) {}
sort(data: number[]) { return this.strategy.sort(data); }
}
I use it for: payment processors, file export formats, notification channels.
Observer
One object notifies many subscribers when its state changes.
// In React: useEffect + event emitters
// In Node: EventEmitter
// In Go: channels
I use it for: React state → UI sync, webhook fan-out, real-time event broadcasts.
Facade
Simplify a complex subsystem behind a single interface.
// Instead of: new S3Client → PutObjectCommand → await → handle errors
// Wrap into:
const storage = new FileStorage();
await storage.upload(key, buffer);
I use it for: third-party SDK wrappers, domain service layers.
Repository
Abstract data access behind an interface. Swap storage implementations without touching business logic.
interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
I use it for: any service that touches a DB — makes testing easier (mock the repo, not the DB).
Patterns I Avoid Overusing
- Singleton — creates hidden global state. Use dependency injection instead.
- Abstract Factory — often overkill; Strategy + simple factory usually enough.
- Chain of Responsibility — middleware pipelines are cleaner in most web frameworks.