Express course · No. 04

Most of them you've already reinvented by hand. A pattern is just the name — so a team can say in one word what would take a paragraph, and recognise the move before building it badly.

Essence only · One picture per pattern · Examples over UML

§ 01

A design pattern is a named, reusable solution to a problem that shows up again and again in code. It's vocabulary, not a library — you don't install it, you recognise it.

A pattern is a name for a move you already make

Footballers don't invent "the give-and-go" mid-match. It has a name, and saying the name is faster than describing the whole move.

Most patterns are things competent developers reinvent by hand anyway. Naming them turns a paragraph of explanation into one word the whole team shares. When someone says "wrap it in a Decorator," everyone sees the shape instantly — and that is the entire point of a pattern: a shared vocabulary for moves you'd make regardless.

Patterns are discovered, not invented

Nobody invented the arch. Builders kept finding it was the strongest way to span a gap — so it got a name.

The Gang of Four didn't design 23 clever tricks; they catalogued solutions that kept reappearing in good code. So a pattern isn't a target to hit — it's a shape you arrive at naturally when the problem calls for it. If you're forcing one in, you probably don't have the problem yet.

Three families, three questions

A kitchen has tools for three jobs: making the food, plating it, and serving it. The patterns split the same way.

Creational patterns are about how objects are born — who builds them, and how. Structural patterns are about how objects fit together into larger shapes. Behavioural patterns are about how objects talk and divide responsibility. Almost every classic pattern is one of those three answers.

A pattern you don't need is just complexity

A Swiss Army knife in your pocket is handy. Bolting all twenty tools onto your front door is not.

Every pattern adds indirection — a layer between you and the thing. That's worth it when it buys flexibility you'll actually use, and pure cost when it doesn't. The common mistake isn't ignorance of patterns; it's reaching for one before the problem exists.

A pattern is a shortcut for a conversation. Use it to be understood, not to look clever.

§ 02

How objects come to life — without scattering new everywhere and welding your code to concrete classes.

Factory: ask for a thing by name, not by recipe

A coffee machine: you press "latte" and get one. You don't grind, froth, and pour yourself — and you don't need to know how it's done.

A factory hands you an object without you calling its constructor directly, so the calling code never depends on the concrete class. Example: createPaymentProvider("stripe") returns something that satisfies a PaymentProvider interface — switch to PayPal by changing the factory, not every call site. It buys the freedom to change what gets built without touching who asked for it.

Abstract Factory: a whole matching family at once

Buying a furniture set — "Victorian" or "Modern" — where every piece arrives in the same style, with no mismatched chairs.

When objects must come in consistent families, an abstract factory builds the whole set so they fit together. Example: a UI toolkit that produces matching buttons, menus, and dialogs for macOS versus Windows from one factory. Powerful — but it's heavy machinery, worth it only when the families are real.

Builder: assemble a complex object step by step

Ordering a custom burger — bun, patty, no onion, extra cheese — one choice at a time, then "done."

When an object has many optional parts, a builder lets you set them piece by piece instead of a monstrous constructor with twelve arguments. Example: QueryBuilder().select("name").where("age > 18").limit(10).build(). It trades one baffling constructor for a readable chain.

Singleton: exactly one, shared by all

A country has one president. Everyone who refers to "the president" means the same person — there aren't two.

A singleton guarantees a single instance with one global access point. Example: a configuration object or logger everyone reads from. But handle with care: a singleton is global state in a nice coat, and global state makes testing and reasoning hard. Often a single instance you pass in explicitly beats one reached through a global.

Prototype: copy a filled-in thing instead of building fresh

Photocopying a completed form is faster than filling a blank one from scratch.

When making an object from scratch is expensive or fiddly, clone an existing one and tweak it. Example: duplicating a configured game enemy, or a styled document node, instead of rebuilding its setup each time. Handy when objects are costly to construct and mostly similar.

Hide the new. The fewer places that know how an object is built, the freer you are to change it.

§ 03

How objects compose into larger structures — fitting incompatible parts together and adding capability without a rewrite.

Adapter: a plug converter between two interfaces

Your laptop charger abroad — the device is fine, the socket is fine; you just need the little converter between them.

An adapter wraps one interface so it looks like another, letting two things that weren't designed to cooperate work together. Example: wrapping a third-party payment SDK so it satisfies your own PaymentProvider interface — your code never sees the vendor's shape. It's how you stop someone else's API from leaking into your whole codebase.

Decorator: add toppings without changing the pizza

A plain pizza, then cheese, then mushrooms — each layer wraps the last and adds something, and it's still a pizza.

A decorator wraps an object to add behaviour without modifying the original or subclassing it. Example: wrapping a data stream to add compression, then encryption — each a layer you can stack or remove. It lets you add features in combinations nobody had to anticipate.

Facade: one simple front desk over a messy back office

A hotel concierge — you say "I need a taxi and a dinner reservation," and they handle the tangle of phone calls behind the counter.

A facade gives a simple, single entry point to a complicated subsystem, hiding its moving parts. Example: a VideoConverter.convert(file, "mp4") that quietly orchestrates codecs, buffers, and audio streams underneath. It shrinks a big surface down to the one door most callers need.

Proxy: a stand-in that controls access

A celebrity's assistant — you don't reach the star directly; the assistant screens, schedules, and sometimes answers for them.

A proxy stands in front of an object to control access to it — for lazy loading, permissions, caching, or talking to something remote. Example: an image proxy that doesn't load the real file until it's shown; or an access proxy that checks permissions before forwarding the call. Same interface, an extra gate in front.

Composite: treat a tree and a leaf the same

A folder holds files and other folders. "Calculate the size" works the same whether you ask one file or the whole tree.

A composite lets you treat individual objects and groups of objects uniformly, so a branch and a leaf share one interface. Example: a UI where a button and a panel-full-of-buttons both answer render() and getSize(). It turns recursive structures into something you handle without special-casing every level.

Composition over inheritance: snap behaviour together at runtime instead of freezing it into a class tree.

§ 04

How objects share work and talk to each other — so responsibility is split cleanly and the right code runs at the right moment.

Strategy: swap the algorithm, keep the caller

A maps app: same trip, but you pick "fastest," "shortest," or "avoid tolls," and it routes differently. You choose the strategy; the app just follows it.

Strategy makes an algorithm interchangeable at runtime, so the caller stays the same while the method varies. Example: a checkout that takes a PricingStrategy — regular, member, or holiday-sale — instead of a growing if ladder. In modern languages this is often just passing a function rather than a whole class — which is the point: the pattern is the idea, not the ceremony.

Observer: subscribe, and get told when it changes

A newsletter — you subscribe once, and every new issue comes to you. The publisher doesn't phone each reader; it just sends to the list.

Observer lets objects subscribe to another and be notified when it changes, without it knowing who they are. Example: a spreadsheet cell that refreshes every chart depending on it; UI frameworks reacting to state; pub/sub everywhere. It's the backbone of event-driven and reactive code.

Command: wrap a request as an object

A restaurant order ticket — the request "table 5, two coffees" becomes a slip you can queue, reorder, log, and even tear up.

Command turns an action into a standalone object, so you can queue it, log it, schedule it, or undo it. Example: every edit in an editor is a command — which is exactly what makes undo/redo and macros possible; job queues are commands waiting their turn. It's how an action becomes something you can store and replay.

Chain of Responsibility: pass it along until someone handles it

Customer support tiers — the front desk tries, escalates to a specialist, then to a manager, until someone can actually solve it.

A request travels along a chain of handlers; each either deals with it or passes it on. Example: web middleware — auth, then logging, then rate-limiting, then the route — each link handling its bit or forwarding. It decouples the sender from whoever ends up doing the work.

Good behavioural patterns let two pieces cooperate without either one hard-wiring the other in.

§ 05

How an object's behaviour changes with its state, how you step through collections, and how you fix a skeleton while leaving one step open.

State: behaviour that changes with the mode you're in

A traffic light — green, yellow, red — where each colour decides what happens now and which colour comes next. Same light, different behaviour per state.

The State pattern lets an object change its behaviour as its internal state changes, replacing a tangle of if and switch with clear states and transitions. Example: an order that behaves differently as pending, paid, shipped, or cancelled, each state knowing only its own rules and what it can become. It turns a mess of flags into a tidy little machine.

Iterator: step through a collection without seeing its insides

A TV remote's next and previous — you move through channels without knowing how they're stored.

Iterator gives a uniform way to walk a collection without exposing how it's built — array, tree, or linked list. Example: for (item of collection) works the same over a list or a custom structure. This one is so useful the language usually has it built infor..of, generators, __iter__ — the clearest sign a pattern can graduate into a feature.

Template Method: a fixed recipe with one blank to fill

A recipe that's identical every time — except "add your choice of spice" at step four. The skeleton is fixed; one step is yours.

Template Method defines the skeleton of an operation and lets the specific steps be overridden without changing the overall shape. Example: a base data importer that opens, reads, parses (you fill this in), and saves — where CSV and JSON importers supply only the parse step. Useful when many flows share a shape but differ in a spot or two.

And the rest, in one breath

A toolbox has tools you reach for monthly, not daily — but you should still know they exist.

A handful more earn their place occasionally: Mediator (a control tower so objects talk through one hub instead of a tangle of direct links), Visitor (add new operations to a structure without editing its classes), Memento (capture an object's state so you can restore it later — save points, undo snapshots). Know the names; reach for them only when the exact problem appears.

When a pattern becomes a language keyword, stop writing the pattern and use the keyword.

§ 06

The catalogue is decades of hard-won wisdom — and also a product of its time. Use it like a senior engineer, not a checklist.

Many patterns are now just language features

We used to give directions by landmarks; now the phone navigates. The skill didn't vanish — it got absorbed into the tool.

Iterator is for..of. Strategy is often a function passed in. Decorator and Observer are first-class in modern frameworks. When the language hands you the shape for free, writing the full pattern by hand is just ceremony. The idea still matters; the boilerplate usually doesn't.

Pattern-itis is a real disease

A cook who empties the whole spice rack into one dish doesn't get a richer meal — they get an inedible one.

The classic over-engineering smell is reaching for patterns to look sophisticated: an abstract factory for one product, a Strategy for one algorithm that never varies, five layers of indirection around an if. And it's worse now — an AI assistant will happily generate a "Multi-Level Abstract Factory" for what should be three lines. Simpler code is almost always the more senior choice.

Patterns are a vocabulary, not a law

Knowing many words doesn't make a good writer. Knowing which word — and when to stay silent — does.

The real value of the catalogue is shared language: "let's put a Facade here" lands instantly with anyone who knows the term, and that is worth a lot on a team. But the goal is always the simplest code that solves the problem. The pattern is a means; naming one is never the achievement.

Learn all the patterns so you can recognise them. Use as few as you can get away with.

§ 07

Patterns are answers. The skill is matching them to a real question — and knowing when no pattern is the right answer.

Let the pattern emerge from the pain

You don't pick a wrench before you've found the bolt. You see the problem, then reach for the tool that fits it.

Good pattern use is usually a refactor, not a starting blueprint: you write the simple thing, feel a specific pain — this if ladder keeps growing, this class knows too much about how it's built — and then the pattern that relieves it is obvious. Forcing patterns up front is how you build cathedrals nobody asked for.

Name it only when the name helps

You don't say "I performed a give-and-go" in casual chat. But on the pitch, with teammates, the word saves a whole sentence.

A pattern's payoff is communication and flexibility. If naming it makes the code clearer to the next person, use the name out loud — in a class name, a comment, a design chat. If the indirection buys nothing, the plainest code wins, pattern or not.

Before reaching for a pattern
  • What recurring problem does it solve? Say it in a sentence. - Do I have that problem now, or am I guessing at the future? - Would a plain function or object do? Try that first.
  • Does my language already give me this for free? - Will naming it make the code clearer to the next person — or just fancier? - Am I refactoring toward it, or forcing it in up front?
Smell tests that you over-engineered
  • You have a factory that builds exactly one thing. - A Strategy with a single strategy that never changes. - More interfaces and indirection than actual behaviour. - You added the pattern to look senior, not to solve a pain. - A new reader needs a UML diagram to follow three lines of logic.
Signs you chose well
  • The pattern removed an if/switch ladder that kept growing. - You can swap an implementation without touching the callers. - A teammate recognised the shape from its name immediately. - The indirection earns its keep — you actually use the flexibility. - The code is simpler with the pattern than without it, not just cleverer.

The best use of a design pattern is the one a reader never notices — because it made the code feel obvious.

End of express course · 7 chapters · patterns over UML

Next comes the source: the Gang of Four's Design Patterns, and its kinder modern companions — Head First Design Patterns, refactoring.guru, Martin Fowler's Refactoring. But read them the way you'd learn chess openings: not to memorise moves, but to recognise the position when it appears on your own board.