Design Patterns in JavaScript
What a Pattern Actually Is
A design pattern is not a library you install. It is a named solution to a recurring problem — a template you can adapt, a vocabulary you can use to communicate with other developers instantly. When you say "use the Observer pattern here", every experienced developer in the room knows exactly what you mean.
The patterns below are not invented for the sake of elegance. Each one exists because the naive approach to the same problem has a specific failure mode.
Singleton — One Instance, Global Access
The problem: some resources should exist exactly once. A database connection pool, a configuration object, a logger. Creating multiple instances is either wasteful or catastrophically wrong.
The private static #instance field is the guard. The constructor returns the existing instance if one exists, making new Config() safe to call anywhere — it always returns the one true instance.
Observer — Decouple Producers from Consumers
The problem: one part of a system changes state. Multiple other parts need to react. Hardcoding the connections makes everything tightly coupled — changing one breaks the others.
The Observer pattern (also called Publish/Subscribe) introduces an intermediary that neither producer nor consumer knows about:
Adding a new consumer means calling session.on(...) — nothing else changes. The producer does not know or care who is listening. This is how React's synthetic events, Node's EventEmitter, and every reactive UI framework work underneath.
Strategy — Swap Algorithms at Runtime
The problem: you have a function that does the same thing in different ways depending on context. Stacking if/else or switch for each variant makes the function grow forever and become untestable.
Strategy extracts each algorithm variant into its own object and swaps them at runtime:
Adding a new grading algorithm means adding a new strategy object — the Grader class does not change at all. This is the Open/Closed Principle in action: open for extension, closed for modification.
Command — Encapsulate Operations as Objects
The problem: you need undo/redo, operation history, queued operations, or the ability to retry failed operations. None of these are possible if your operations are just function calls that disappear after execution.
Command turns each operation into an object that can be stored, queued, reversed, and replayed:
Every text editor, design tool, and spreadsheet application is built on this pattern. The history stack is just an array of Command objects — undo walks it backwards, redo walks it forwards.
Decorator — Add Behaviour Without Subclassing
The problem: you want to add capabilities to an object — logging, caching, validation, retrying — without modifying the original class or creating an exponential hierarchy of subclasses.
Decorator wraps an object, implements the same interface, and adds behaviour before or after delegating to the original:
Three independent behaviours — logging, caching, retry — each in its own class, each composable with any service that implements .find(). No inheritance anywhere. To add rate limiting, write one more decorator and wrap it in.
Builder — Construct Complex Objects Step by Step
The problem: an object needs many optional parameters. A constructor with twelve arguments is unreadable. A Builder accumulates configuration fluently and constructs only when you call .build():
Every call returns this — enabling method chaining. The actual SQL is assembled only in .build(). ORMs like Knex.js and Prisma's query API are exactly this pattern.
Choosing the Right Pattern
What You Have Learned
Design patterns are named solutions to problems that repeat across every codebase, every language, every decade. Knowing their names and shapes lets you recognise problems faster and communicate solutions instantly.
The key ideas:
- Singleton: one instance, returned by every call to
neworgetInstance()— use for shared global resources - Observer: producers emit events, consumers subscribe independently — decouples things that change from things that react
- Strategy: swap the algorithm, keep the context — eliminates
if/elsechains that grow forever - Command: operations as objects with
execute()andundo()— enables history, queuing, and replay - Decorator: wrap an object, add behaviour, delegate the rest — cross-cutting concerns without inheritance
- Builder: accumulate configuration with method chaining, construct only at
.build()— readable complex object creation - Patterns compose — the final runner combined Observer and Strategy in twelve lines
In the next lesson: Testing JavaScript — unit tests, property-based testing, mocking, and the discipline that turns "I think this works" into "I can prove this works under these conditions."