Series · 10 parts
Design Patterns in TypeScript
The classic design patterns every senior web engineer should have at hand, explained with runnable TypeScript.
-
When (and when not) to share a single instance: the Singleton pattern, why ESM modules are already singletons, lazy init, the testability trap, and typed implementations — with exercises.
-
Centralize object creation: factory functions over `new`, discriminated-union driven factories, the Abstract Factory for families of related objects, and where this beats classes in TypeScript.
-
Construct complex objects step by step: fluent method chaining, immutable builders, the type-safe staged (phantom-type) builder that makes illegal states unrepresentable, and when an options object is enough.
-
Swap an algorithm at runtime without touching its caller: the Strategy pattern, why a map of functions is the idiomatic TS form, replacing sprawling if/switch, and injecting behavior for testability.
-
Decouple "something changed" from "who reacts": the Observer pattern, a fully typed event emitter, the platform EventTarget, Pub/Sub via an event bus, and how signals/reactivity build on the same idea.
-
Add behavior without touching the original: the Decorator pattern via higher-order functions, wrapping a service to add caching/logging/retry, the middleware chain, and how TS decorators (and the new standard) compare.
-
Tame third-party and legacy code: the Adapter that makes an incompatible API fit your interface, the Facade that hides a messy subsystem behind one entry point, and the anti-corruption layer that keeps vendor types out of your domain.
-
Turn actions into data: the Command pattern for dispatch, queues, and undo/redo, the Memento for snapshotting state, and how reducers (Redux-style) are commands in disguise.
-
Make impossible states impossible: the State pattern, modeling UI/lifecycle with a finite state machine, type-safe transitions via discriminated unions, and why this kills "loading && error" bugs.
-
Control access and wire your app for testability: the JS Proxy for reactivity/validation/lazy loading, Dependency Injection and Inversion of Control, the composition root, and choosing constructor injection over service-locator.