What a Pattern Actually Is (and Isn't)
The worst code I ever inherited had a class called PriceFormatterStrategyFactory. It formatted prices. Cents went in, a string like $12.00 came out, and that was the whole job. To do it you asked the factory for a formatter, which handed you a strategy, which had one method, which ran the two lines of arithmetic a plain function does. Four files. An interface. A unit test that checked the factory returned a formatter and asserted nothing else.
The same codebase, in a different corner, had the opposite disease. When an order shipped, the app had to email the customer, mark the invoice paid, and log an analytics event. That exact “when this happens, do these unrelated things” showed up in four places, written four different ways, each one a tangle of direct calls copied and edited until it drifted out of sync. Nobody had noticed it was all the same shape. Nobody had a word for it.
Two bugs, pointing in opposite directions. One had a pattern bolted onto a problem far too small to need it. The other had a genuinely recurring problem and no pattern at all, so it got reinvented, badly, on loop. This entire part of the course lives in the gap between those two mistakes, and getting good at patterns mostly means learning to feel where that gap is.
So what is a pattern, really
Strip away the mystique first. A design pattern is not something you install. There is no npm i observer. It is not a rule you are obligated to follow, and it is not a mark of seniority to have used a lot of them. A pattern is a named, reusable solution to a problem that keeps showing up. That is the entire definition.
The reusable-solution half gets all the attention. The named half is where the value actually lives.
Watch what happens in a real design discussion. Someone is three minutes into explaining how the cart badge, the order total, and the analytics call all need to update whenever the cart changes, and they are drawing boxes, and you say “so it’s an observer.” The drawing stops. Everyone now shares a mental model of the whole thing: a subject, some watchers, a notify-on-change. Two words replaced three minutes and a whiteboard. That is what a pattern buys you, and it is worth more than any specific code.
Where the catalogue came from
Most of the named patterns you will hear about trace back to one 1994 book by four authors, forever after “the Gang of Four.” They did not invent the patterns. They went looking at good object-oriented systems, mostly C++ and Smalltalk, noticed the same designs recurring, and wrote them down with names and trade-offs. Twenty-three of them, sorted into three buckets.
That cataloguing act was the real contribution. Suddenly a whole industry could say “factory” or “decorator” and mean the same thing. The names outlived the specific code by a wide margin, which is the tell that the vocabulary was always the point.
Why half the catalogue shrinks in JavaScript
Here is the thing nobody tells you when they hand you a patterns book written for Java. A big chunk of that catalogue exists to work around limitations JavaScript does not have.
In a language where functions are not first-class, “pass some behaviour to be chosen at runtime” is a hard problem, and you solve it by defining an interface, writing a class per behaviour, and an object to hold the current one. That is the Strategy pattern, and in Java it earns every line. Watch the same thing translated literally into JavaScript by someone who learned it there:
class ShippingStrategy {
calculate(order) { throw new Error("not implemented"); }
}
class StandardShipping extends ShippingStrategy {
calculate(order) { return order.weight * 0.5; }
}
class ExpressShipping extends ShippingStrategy {
calculate(order) { return order.weight * 0.5 + 10; }
}
class ShippingContext {
constructor(strategy) { this.strategy = strategy; }
setStrategy(strategy) { this.strategy = strategy; }
cost(order) { return this.strategy.calculate(order); }
}
const ctx = new ShippingContext(new ExpressShipping());
ctx.cost(order);
Four classes and an inheritance tree to choose between three formulas. Now the JavaScript version, which is the same pattern, because a strategy is just interchangeable behaviour picked at runtime:
const shipping = {
standard: (order) => order.weight * 0.5,
express: (order) => order.weight * 0.5 + 10,
overnight: (order) => order.weight * 0.9 + 25,
};
const cost = shipping[order.method](order);
The pattern did not disappear. Swappable behaviour, chosen by a key, is still Strategy. What disappeared was the ceremony, because functions are values here, so “a behaviour you can pass around” needs no scaffolding. The full Strategy write-up goes deeper, but the shrink is the headline.
Singleton tells the same story, harder. In class-based languages a Singleton is a whole apparatus: a private constructor, a static field, a getInstance() that guards against a second instance. In JavaScript the module system already does exactly that. A module is evaluated once, and every import shares the same bindings, so a module is a singleton, for free.
// db.js. This module is your singleton, guaranteed by the loader.
let pool;
export function getPool() {
return (pool ??= createPool());
}
No class, no getInstance, no double-checked locking. Import getPool from anywhere and you get the same pool. If you catch yourself writing class Config { static #instance ... }, stop, because you are rebuilding something ES modules hand you already. The module pattern is the JavaScript-native version of this whole idea.
The over-engineering trap
A pattern is a tool for managing complexity. Read that sentence again, because it contains its own warning. If there is no complexity to manage, the pattern is not helping, it is adding the very thing it was supposed to remove. That is the PriceFormatterStrategyFactory from the opening, and it is a real bug, not a style opinion. Every layer of indirection is a thing the next reader has to load into their head before they can change one line.
The honest way to see it is a cost curve. Machinery has an upfront price you pay whether or not the problem is hard. Ad-hoc code is nearly free at first and gets punishing as the problem grows. They cross somewhere, and where you sit relative to that crossing is the whole question.
So how do you know which side of the line you are on? You do not, not from one look. Which is why the most useful rule in this whole part is also the oldest.
Extract a pattern the third time you see the problem, not the first. Two occurrences of similar code look like duplication, but you cannot draw a trend through two points. The third occurrence is the one that tells you whether it is a real recurring shape or two things that happen to rhyme this week. Wait for it.
Anti-patterns: the named bad solutions
If a pattern is a named good solution, an anti-pattern is its mirror: a named bad one that keeps getting reinvented because it feels reasonable in the moment. Naming these is just as useful as naming the good ones, because “this is turning into a god object” ends an argument faster than a paragraph of concern.
A few worth knowing on sight:
- God object. One class or module that grows until it knows everything and touches everything. Business rules, database access, formatting, and validation all pile into
OrderManager, and eventually nobody can change one part without risking the rest. - Spaghetti code. No structure to speak of, control flow that loops back on itself, state mutated from everywhere. You cannot trace a value to its source without reading the whole file.
- Golden hammer. You learned a tool, it worked once, and now every problem looks shaped for it. The person who just discovered observers and wants to make everything an event lives here.
Put the good and the bad side by side on one problem and the difference is stark. Take “when an order ships, several things must happen.” The observer keeps every reaction as its own small, testable unit. The god object crams them all into one class that shares mutable state and cannot be pulled apart.
The event-driven architecture chapter is the observer grown up to application scale, and it exists largely to keep god objects from forming. Notice this is a design question, not a UI one. It has nothing to do with clicking a button on a page, which is DOM event handling and a different topic entirely.
Name that pattern
Reading about patterns is not the skill. The skill is looking at a lump of code or a described problem and knowing which shape fits, including the cases where the honest answer is “none, this is too simple.” Try it. One of these is a trap.
Question two is the one that catches people. Once you know a few patterns, the reflex is to fit one to every problem, and the discipline is noticing when the code is already as simple as it should be.
How to actually use this part
The rest of Part 8 walks through the patterns worth knowing in modern JavaScript: strategy, module, decorator, proxy, chain of responsibility, event-driven architecture, and more. Read them for the shape and the problem each one solves, not as a checklist to apply.
The goal is recognition, in two directions. When you are about to write the same thing a third time, you want to feel it and reach for the pattern that fits. And when someone hands you a PriceFormatterStrategyFactory, you want to feel that too, and say so. Both are the same skill: matching real code to a named shape, and knowing when no named shape belongs.
Do not go looking for places to install patterns. Nobody was ever thanked for turning three lines into a design pattern. Learn the shapes so you can name what is already there, extract one when the third occurrence proves you need it, and leave the simple code simple.
Summary
- A design pattern is a named, reusable solution to a recurring problem. It is not a library, not a rule, and not a seniority badge. The naming is the biggest part of the value.
- The real payoff is shared vocabulary. Saying “this is an observer” communicates an entire design in two words, in review and in architecture talks alike.
- The Gang of Four catalogued recurring object-oriented designs, they did not invent them. The names outlived the specific code, which tells you what mattered.
- Half the catalogue shrinks in JavaScript. First-class functions, closures, modules, and objects-without-classes mean a Strategy is an object of functions and a Singleton is a module. The pattern stays, the ceremony goes.
- Over-engineering is a real bug. A pattern manages complexity, so applied to simple code it only adds complexity. The
SomethingFactoryStrategyManagerfor a two-line job is the classic tell. - Rule of three: extract a pattern the third time you meet the problem, not the first. Duplication is cheaper than the wrong abstraction, because the wrong abstraction taxes every future change.
- Anti-patterns are named bad solutions: god object, spaghetti, golden hammer. Learn them so you can name the smell in review as fast as you name the good shape.
- When to reach for a pattern: you have seen the same problem three times and the ad-hoc versions are drifting or fighting you. When to avoid one: the problem is small, occurs once, or the pattern would add more machinery than the problem is worth.