Which patterns the 22 rounds use, where each one is easiest to see, and the six rounds to read first if you can't read all 22.
By now you have 25 pattern lessons behind you and 22 case studies ahead. Each case study already says, in its own "patterns" table, which patterns it uses. What none of them can tell you is how the 22 fit together: which patterns are everywhere and which are rare, which rounds are crossroads where many patterns meet, and which pattern you will meet only once. That needs one table.
This lesson is that table. It has the matrix (rounds down the side, pattern lessons across the top), the same information in words with the class names, a short program that counts it, and the route through it that meets every pattern in the fewest rounds. Each of the 22 case studies now carries a "Patterns used here" box that links back to the pattern lessons; this lesson is the view from the other side.
The pattern map is a metro map: each pattern is a line, each case study a station, and the pub-sub logger is an interchange where six lines meet.
The matrix#
Each column is a pattern lesson, by its number: 01 composition and value objects, 04 dependency injection, 06 Singleton (and registries), 07 Factory, 10 Adapter, 11 Decorator, 12 Facade, 13 Composite, 14 Proxy, 16 Strategy, 17 Observer, 18 Command, 19 State, 21 Chain of Responsibility, 22 Iterator, 23 Memento, 24 Visitor and 25 the concurrency patterns. Each row is a case study. A ● means the round's own code or patterns table builds it; a ○ means it appears only as a follow-up, "light", "optional" or "avoided" in that round.
| Round | 01 | 04 | 06 | 07 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01 Parking lot | ○ | ● | ○ | ● | ○ | ● | ○ | ● | ||||||||||
| 02 LRU cache | ● | ● | ○ | |||||||||||||||
| 03 Rate limiter | ● | ● | ● | ● | ||||||||||||||
| 04 Elevator | ● | ● | ○ | ● | ||||||||||||||
| 05 Movie booking | ● | ● | ● | ● | ● | |||||||||||||
| 06 Splitwise | ● | ● | ● | |||||||||||||||
| 07 Vending machine | ○ | ○ | ● | |||||||||||||||
| 08 Pub-sub logger | ● | ● | ● | ● | ● | ● | ||||||||||||
| 09 Snake and ladder | ● | ● | ● | ● | ||||||||||||||
| 10 Chess | ● | ● | ● | ● | ||||||||||||||
| 11 ATM | ● | ● | ○ | ● | ||||||||||||||
| 12 Library | ● | ● | ● | ● | ||||||||||||||
| 13 Hotel | ● | ● | ● | |||||||||||||||
| 14 Ride sharing | ● | ● | ● | ● | ● | |||||||||||||
| 15 Food delivery | ● | ● | ● | ● | ||||||||||||||
| 16 Meeting rooms | ● | ● | ○ | |||||||||||||||
| 17 File system | ● | ● | ○ | ○ | ● | |||||||||||||
| 18 Text editor | ● | ● | ● | ● | ||||||||||||||
| 19 Auction | ● | ● | ● | |||||||||||||||
| 20 Order book | ● | ● | ● | |||||||||||||||
| 21 Task scheduler | ● | ● | ||||||||||||||||
| 22 Key-value store | ● | ● | ● |
How to read it. Scan down a column to see where a pattern lives: Strategy (16) is filled in nearly every row, the concurrency patterns (25) in ten, State (19) in nine, and Visitor (24) is hollow in its one row. Scan along a row to see a round's mix: the pub-sub logger has six filled circles, the LRU cache two filled and one hollow. Only the patterns that have a lesson are on the map. The case studies also use ideas that are not GoF patterns, such as idempotency keys, lazy deletion and a heap with stale entries; they are in the rounds' own tables and not here.
Read the matrix down a column to see where a pattern lives, along a row to see a round's mix; filled circles are built, hollow ones only mentioned.
Counting the map in code#
Rather than count the circles by eye, here is the matrix as data and a program that counts it. Each row is a round and its lesson numbers, with o before a lesson that is only light or a follow-up. The program prints how many rounds use each pattern, which rounds have no Strategy, which pattern only ever appears as a follow-up, and a shortest route that covers every built pattern:
# pattern_map.py: the 22 rounds against the pattern lessons, counted from one table
# "o" before a lesson number means the pattern is light, optional, avoided or only a follow-up in that round.
ROUNDS = [
"01|Parking lot|16 04 07 25 o06 o17 o11 o01",
"02|LRU cache|16 01 o17",
"03|Rate limiter|16 07 04 06",
"04|Elevator|16 19 17 o18",
"05|Movie booking|16 10 19 12 25",
"06|Splitwise|16 12 01",
"07|Vending machine|19 o16 o17",
"08|Pub-sub logger|21 17 16 11 06 25",
"09|Snake and ladder|16 17 04 01",
"10|Chess|01 18 22 25",
"11|ATM|19 10 04 o17",
"12|Library|16 17 19 12",
"13|Hotel|16 19 01",
"14|Ride sharing|16 11 19 12 25",
"15|Food delivery|19 16 10 12",
"16|Meeting rooms|16 o17 01",
"17|File system|13 12 25 o22 o24",
"18|Text editor|18 13 23 25",
"19|Auction|19 17 16",
"20|Order book|18 25 01",
"21|Task scheduler|25 16",
"22|Key-value store|16 14 25",
]
NAMES = {
1: "Composition and values",
4: "Dependency injection",
6: "Singleton and registry",
7: "Factory",
10: "Adapter",
11: "Decorator",
12: "Facade",
13: "Composite",
14: "Proxy",
16: "Strategy",
17: "Observer",
18: "Command",
19: "State",
21: "Chain of Responsibility",
22: "Iterator",
23: "Memento",
24: "Visitor",
25: "Concurrency",
}
def parse(row):
num, name, marks = row.split("|")
main, light = [], []
for tok in marks.split():
(light if tok.startswith("o") else main).append(int(tok.lstrip("o")))
return "lld-" + num, name, main, light
rounds = [parse(r) for r in ROUNDS]
total, built = {}, {}
for code, name, main, light in rounds:
for n in main:
total[n] = total.get(n, 0) + 1
built[n] = built.get(n, 0) + 1
for n in light:
total[n] = total.get(n, 0) + 1
print(f"rounds: {len(rounds)}, pattern lessons: {len(total)}")
for n in sorted(total, key=lambda n: (-total[n], n)):
print(f"{n:02d} {NAMES[n]}: in {total[n]}, main in {built.get(n, 0)}")
print("rounds without Strategy:", ", ".join(c for c, _, main, light in rounds if 16 not in main + light))
only_light = sorted(n for n in total if n not in built)
print("only light or follow-up:", ", ".join(f"{NAMES[n]} ({n})" for n in only_light))
covered, left, order = set(), list(rounds), []
while covered != set(built):
best = max(left, key=lambda r: (len(set(r[2]) - covered), -int(r[0][4:])))
order.append((best, sorted(set(best[2]) - covered)))
covered |= set(best[2])
left.remove(best)
print(f"{len(order)} rounds cover all {len(built)} built patterns:")
for (code, name, main, light), new in order:
print(f" {code} {name} adds {len(new)}: " + " ".join(f"{n:02d}" for n in new))// PatternMap.java: the 22 rounds against the pattern lessons, counted from one table
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
class PatternMap {
// "o" before a lesson number means the pattern is light, optional, avoided or only a follow-up in that round.
static final String[] ROUNDS = {
"01|Parking lot|16 04 07 25 o06 o17 o11 o01",
"02|LRU cache|16 01 o17",
"03|Rate limiter|16 07 04 06",
"04|Elevator|16 19 17 o18",
"05|Movie booking|16 10 19 12 25",
"06|Splitwise|16 12 01",
"07|Vending machine|19 o16 o17",
"08|Pub-sub logger|21 17 16 11 06 25",
"09|Snake and ladder|16 17 04 01",
"10|Chess|01 18 22 25",
"11|ATM|19 10 04 o17",
"12|Library|16 17 19 12",
"13|Hotel|16 19 01",
"14|Ride sharing|16 11 19 12 25",
"15|Food delivery|19 16 10 12",
"16|Meeting rooms|16 o17 01",
"17|File system|13 12 25 o22 o24",
"18|Text editor|18 13 23 25",
"19|Auction|19 17 16",
"20|Order book|18 25 01",
"21|Task scheduler|25 16",
"22|Key-value store|16 14 25",
};
static final Map<Integer, String> NAMES = new HashMap<>();
static {
NAMES.put(1, "Composition and values");
NAMES.put(4, "Dependency injection");
NAMES.put(6, "Singleton and registry");
NAMES.put(7, "Factory");
NAMES.put(10, "Adapter");
NAMES.put(11, "Decorator");
NAMES.put(12, "Facade");
NAMES.put(13, "Composite");
NAMES.put(14, "Proxy");
NAMES.put(16, "Strategy");
NAMES.put(17, "Observer");
NAMES.put(18, "Command");
NAMES.put(19, "State");
NAMES.put(21, "Chain of Responsibility");
NAMES.put(22, "Iterator");
NAMES.put(23, "Memento");
NAMES.put(24, "Visitor");
NAMES.put(25, "Concurrency");
}
record Round(String code, String name, List<Integer> main, List<Integer> light) {}
static Round parse(String row) {
String[] p = row.split("\\|");
List<Integer> main = new ArrayList<>(), light = new ArrayList<>();
for (String tok : p[2].split(" ")) {
if (tok.startsWith("o")) light.add(Integer.parseInt(tok.substring(1)));
else main.add(Integer.parseInt(tok));
}
return new Round("lld-" + p[0], p[1], main, light);
}
static String two(int n) { return String.format("%02d", n); }
public static void main(String[] args) {
List<Round> rounds = new ArrayList<>();
for (String r : ROUNDS) rounds.add(parse(r));
Map<Integer, Integer> total = new HashMap<>(), built = new HashMap<>();
for (Round r : rounds) {
for (int n : r.main()) { total.merge(n, 1, Integer::sum); built.merge(n, 1, Integer::sum); }
for (int n : r.light()) total.merge(n, 1, Integer::sum);
}
System.out.println("rounds: " + rounds.size() + ", pattern lessons: " + total.size());
List<Integer> lessons = new ArrayList<>(total.keySet());
lessons.sort((a, b) -> total.get(a).equals(total.get(b)) ? a - b : total.get(b) - total.get(a));
for (int n : lessons) {
System.out.println(two(n) + " " + NAMES.get(n) + ": in " + total.get(n) + ", main in " + built.getOrDefault(n, 0));
}
List<String> noStrategy = new ArrayList<>();
for (Round r : rounds) if (!r.main().contains(16) && !r.light().contains(16)) noStrategy.add(r.code());
System.out.println("rounds without Strategy: " + String.join(", ", noStrategy));
List<String> onlyLight = new ArrayList<>();
for (int n : new TreeSet<>(total.keySet())) if (!built.containsKey(n)) onlyLight.add(NAMES.get(n) + " (" + n + ")");
System.out.println("only light or follow-up: " + String.join(", ", onlyLight));
Set<Integer> covered = new HashSet<>();
List<Round> left = new ArrayList<>(rounds);
List<String> out = new ArrayList<>();
while (!covered.equals(built.keySet())) {
Round best = null;
int bestNew = -1;
for (Round r : left) {
Set<Integer> fresh = new HashSet<>(r.main());
fresh.removeAll(covered);
if (fresh.size() > bestNew) { best = r; bestNew = fresh.size(); }
}
Set<Integer> fresh = new TreeSet<>(best.main());
fresh.removeAll(covered);
StringBuilder sb = new StringBuilder(" " + best.code() + " " + best.name() + " adds " + fresh.size() + ":");
for (int n : fresh) sb.append(" ").append(two(n));
out.add(sb.toString());
covered.addAll(best.main());
left.remove(best);
}
System.out.println(out.size() + " rounds cover all " + built.size() + " built patterns:");
for (String line : out) System.out.println(line);
}
}// pattern_map.cpp: the 22 rounds against the pattern lessons, counted from one table
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <map>
#include <set>
#include <sstream>
#include <string>
#include <vector>
// "o" before a lesson number means the pattern is light, optional, avoided or only a follow-up in that round.
const std::vector<std::string> ROUNDS = {
"01|Parking lot|16 04 07 25 o06 o17 o11 o01",
"02|LRU cache|16 01 o17",
"03|Rate limiter|16 07 04 06",
"04|Elevator|16 19 17 o18",
"05|Movie booking|16 10 19 12 25",
"06|Splitwise|16 12 01",
"07|Vending machine|19 o16 o17",
"08|Pub-sub logger|21 17 16 11 06 25",
"09|Snake and ladder|16 17 04 01",
"10|Chess|01 18 22 25",
"11|ATM|19 10 04 o17",
"12|Library|16 17 19 12",
"13|Hotel|16 19 01",
"14|Ride sharing|16 11 19 12 25",
"15|Food delivery|19 16 10 12",
"16|Meeting rooms|16 o17 01",
"17|File system|13 12 25 o22 o24",
"18|Text editor|18 13 23 25",
"19|Auction|19 17 16",
"20|Order book|18 25 01",
"21|Task scheduler|25 16",
"22|Key-value store|16 14 25",
};
const std::map<int, std::string> NAMES = {
{1, "Composition and values"},
{4, "Dependency injection"},
{6, "Singleton and registry"},
{7, "Factory"},
{10, "Adapter"},
{11, "Decorator"},
{12, "Facade"},
{13, "Composite"},
{14, "Proxy"},
{16, "Strategy"},
{17, "Observer"},
{18, "Command"},
{19, "State"},
{21, "Chain of Responsibility"},
{22, "Iterator"},
{23, "Memento"},
{24, "Visitor"},
{25, "Concurrency"},
};
struct Round {
std::string code, name;
std::set<int> main, light;
};
Round parse(const std::string& row) {
std::vector<std::string> p;
std::stringstream ss(row);
for (std::string part; std::getline(ss, part, '|');) p.push_back(part);
Round r{"lld-" + p[0], p[1], {}, {}};
std::stringstream marks(p[2]);
for (std::string tok; marks >> tok;) {
if (tok[0] == 'o') r.light.insert(std::stoi(tok.substr(1)));
else r.main.insert(std::stoi(tok));
}
return r;
}
std::string two(int n) {
std::ostringstream o;
o << std::setw(2) << std::setfill('0') << n;
return o.str();
}
int main() {
std::vector<Round> rounds;
for (const auto& r : ROUNDS) rounds.push_back(parse(r));
std::map<int, int> total, built;
for (const Round& r : rounds) {
for (int n : r.main) { total[n]++; built[n]++; }
for (int n : r.light) total[n]++;
}
std::cout << "rounds: " << rounds.size() << ", pattern lessons: " << total.size() << "\n";
std::vector<int> lessons;
for (const auto& kv : total) lessons.push_back(kv.first);
std::sort(lessons.begin(), lessons.end(), [&](int a, int b) { return total[a] != total[b] ? total[a] > total[b] : a < b; });
for (int n : lessons) {
std::cout << two(n) << " " << NAMES.at(n) << ": in " << total[n] << ", main in " << (built.count(n) ? built[n] : 0) << "\n";
}
std::string noStrategy;
for (const Round& r : rounds) {
if (!r.main.count(16) && !r.light.count(16)) noStrategy += (noStrategy.empty() ? "" : ", ") + r.code;
}
std::cout << "rounds without Strategy: " << noStrategy << "\n";
std::string onlyLight;
for (const auto& kv : total) {
if (!built.count(kv.first)) onlyLight += (onlyLight.empty() ? "" : ", ") + NAMES.at(kv.first) + " (" + std::to_string(kv.first) + ")";
}
std::cout << "only light or follow-up: " << onlyLight << "\n";
std::set<int> covered;
std::vector<Round> left = rounds;
std::vector<std::string> out;
while (covered.size() != built.size()) {
size_t best = 0;
int bestNew = -1;
for (size_t i = 0; i < left.size(); i++) {
int fresh = 0;
for (int n : left[i].main) fresh += covered.count(n) ? 0 : 1;
if (fresh > bestNew) { best = i; bestNew = fresh; }
}
std::string line = " " + left[best].code + " " + left[best].name + " adds " + std::to_string(bestNew) + ":";
for (int n : left[best].main) if (!covered.count(n)) line += " " + two(n);
out.push_back(line);
covered.insert(left[best].main.begin(), left[best].main.end());
left.erase(left.begin() + best);
}
std::cout << out.size() << " rounds cover all " << built.size() << " built patterns:\n";
for (const auto& line : out) std::cout << line << "\n";
}// patternMap.js: the 22 rounds against the pattern lessons, counted from one table
// "o" before a lesson number means the pattern is light, optional, avoided or only a follow-up in that round.
const ROUNDS = [
"01|Parking lot|16 04 07 25 o06 o17 o11 o01",
"02|LRU cache|16 01 o17",
"03|Rate limiter|16 07 04 06",
"04|Elevator|16 19 17 o18",
"05|Movie booking|16 10 19 12 25",
"06|Splitwise|16 12 01",
"07|Vending machine|19 o16 o17",
"08|Pub-sub logger|21 17 16 11 06 25",
"09|Snake and ladder|16 17 04 01",
"10|Chess|01 18 22 25",
"11|ATM|19 10 04 o17",
"12|Library|16 17 19 12",
"13|Hotel|16 19 01",
"14|Ride sharing|16 11 19 12 25",
"15|Food delivery|19 16 10 12",
"16|Meeting rooms|16 o17 01",
"17|File system|13 12 25 o22 o24",
"18|Text editor|18 13 23 25",
"19|Auction|19 17 16",
"20|Order book|18 25 01",
"21|Task scheduler|25 16",
"22|Key-value store|16 14 25",
];
const NAMES = new Map([
[1, "Composition and values"],
[4, "Dependency injection"],
[6, "Singleton and registry"],
[7, "Factory"],
[10, "Adapter"],
[11, "Decorator"],
[12, "Facade"],
[13, "Composite"],
[14, "Proxy"],
[16, "Strategy"],
[17, "Observer"],
[18, "Command"],
[19, "State"],
[21, "Chain of Responsibility"],
[22, "Iterator"],
[23, "Memento"],
[24, "Visitor"],
[25, "Concurrency"],
]);
function parse(row) {
const [num, name, marks] = row.split("|");
const main = [], light = [];
for (const tok of marks.split(" ")) {
if (tok.startsWith("o")) light.push(Number(tok.slice(1)));
else main.push(Number(tok));
}
return { code: "lld-" + num, name, main, light };
}
const two = (n) => String(n).padStart(2, "0");
const rounds = ROUNDS.map(parse);
const total = new Map(), built = new Map();
const bump = (m, n) => m.set(n, (m.get(n) || 0) + 1);
for (const r of rounds) {
for (const n of r.main) { bump(total, n); bump(built, n); }
for (const n of r.light) bump(total, n);
}
console.log(`rounds: ${rounds.length}, pattern lessons: ${total.size}`);
const lessons = [...total.keys()].sort((a, b) => (total.get(a) === total.get(b) ? a - b : total.get(b) - total.get(a)));
for (const n of lessons) console.log(`${two(n)} ${NAMES.get(n)}: in ${total.get(n)}, main in ${built.get(n) || 0}`);
console.log("rounds without Strategy:", rounds.filter((r) => !r.main.includes(16) && !r.light.includes(16)).map((r) => r.code).join(", "));
console.log("only light or follow-up:", [...total.keys()].filter((n) => !built.has(n)).sort((a, b) => a - b).map((n) => `${NAMES.get(n)} (${n})`).join(", "));
const covered = new Set();
let left = [...rounds];
const out = [];
while (covered.size !== built.size) {
let best = null, bestNew = -1;
for (const r of left) {
const fresh = r.main.filter((n) => !covered.has(n)).length;
if (fresh > bestNew) { best = r; bestNew = fresh; }
}
const fresh = best.main.filter((n) => !covered.has(n)).sort((a, b) => a - b);
out.push(` ${best.code} ${best.name} adds ${fresh.length}: ` + fresh.map(two).join(" "));
best.main.forEach((n) => covered.add(n));
left = left.filter((r) => r !== best);
}
console.log(`${out.length} rounds cover all ${built.size} built patterns:`);
for (const line of out) console.log(line);All four print the same thing:
rounds: 22, pattern lessons: 18
16 Strategy: in 17, main in 16
17 Observer: in 10, main in 5
25 Concurrency: in 10, main in 10
19 State: in 9, main in 9
01 Composition and values: in 8, main in 7
12 Facade: in 6, main in 6
04 Dependency injection: in 4, main in 4
18 Command: in 4, main in 3
06 Singleton and registry: in 3, main in 2
10 Adapter: in 3, main in 3
11 Decorator: in 3, main in 2
07 Factory: in 2, main in 2
13 Composite: in 2, main in 2
22 Iterator: in 2, main in 1
14 Proxy: in 1, main in 1
21 Chain of Responsibility: in 1, main in 1
23 Memento: in 1, main in 1
24 Visitor: in 1, main in 0
rounds without Strategy: lld-10, lld-11, lld-17, lld-18, lld-20
only light or follow-up: Visitor (24)
6 rounds cover all 17 built patterns:
lld-08 Pub-sub logger adds 6: 06 11 16 17 21 25
lld-05 Movie booking adds 3: 10 12 19
lld-10 Chess adds 3: 01 18 22
lld-01 Parking lot adds 2: 04 07
lld-18 Text editor adds 2: 13 23
lld-22 Key-value store adds 1: 14Check two lines by hand. Strategy is in 17 rounds: the 16 with a filled circle in column 16 (01, 02, 03, 04, 05, 06, 08, 09, 12, 13, 14, 15, 16, 19, 21 and 22), plus the vending machine, where it is optional ("say it; don't build it"). Observer is in 10 rounds but main in 5: the elevator, the logger, snake and ladder, the library and the auction build it, while the parking lot, the vending machine and the ATM raise it only as a follow-up, and the LRU cache and the meeting rooms use a light form.
What each language brings to the same program:
- Python:
str.split, a list of tuples andmaxwith a key do most of the work. The key(new patterns, minus the round number)is how the tie-break "lower round first" is written. - Java: a
recordfor a round,HashMap.mergefor counting, and an explicit loop for the choice of the next round, because ties must go to the first (lowest) round. - C++:
std::mapandstd::setkeep their keys sorted, which makes the output stable. The samefresh > bestcomparison keeps the first of a tie. - JavaScript:
Mapfor the counts andArray.sortwith a comparator for the order;padStartwrites06.
What the counts say#
Five things stand out.
Strategy is the default. It is in 17 of 22 rounds, and in 16 of them the round builds it. If you remember one pattern from the whole part, this is it, and its lesson is the Strategy pattern.
Observer is mentioned more than it is built. It is in 10 rounds but central in 5. In the other five it is a follow-up or "light": the parking lot's display board, the LRU cache's on_evict callback, the vending machine's remote reporting, the ATM's low-cash alert and the meeting rooms' Notifier.
State is mostly an enum or a table. All nine rounds with State build it, but only the vending machine uses the full class-per-state pattern. The others use an enum, or an enum with a transition table, and the State lesson says why.
Concurrency is common, and it is a family. Ten rounds build one of the concurrency patterns, from the parking lot's one lock and the logger's drop-don't-block queue to the order book's single writer, and the concurrency lesson sorts them.
Five rounds have no Strategy at all. They show that the map follows the prompt, not a template:
| Round | What it uses instead |
|---|---|
| Chess | Inheritance for families, a command with an undo stack, iterators |
| ATM | State as an enum with guards, an adapter for Bank, a test double |
| File system | Composite and Facade; Iterator and Visitor as a follow-up |
| Text editor | Command, Composite, Memento |
| Order book | A command queue, a single writer, value objects |
And one pattern is on the map only as a follow-up: Visitor appears once, in the file system, where find and disk usage become visitors of one traversal. It has a lesson, the Visitor lesson, but if time is short it is the last thing to rehearse.
The map in words#
The matrix says where; this table says with which class. It lists the same patterns as each case study's "Patterns used here" box, in short (the boxes add the class names), so if a box and this table ever disagree, one of them is wrong:
| Case study | Patterns used here (lesson) |
|---|---|
| lld-01 Parking lot | Strategy (16), Dependency injection (04), Factory function (07), One lock (25), Singleton, avoided (06), Vehicle subclasses, avoided (01), Observer, follow-up (17), Decorator, follow-up (11) |
| lld-02 LRU cache | Strategy (16), Composition over inheritance (01), Observer, light (17) |
| lld-03 Rate limiter | Strategy (16), Factory (07), Dependency injection: Clock (04), Registry with lazy creation (06) |
| lld-04 Elevator | Strategy (16), State as an enum (19), Observer (17), Command, light (18) |
| lld-05 Movie booking | Strategy / Adapter: SeatLockProvider (16, 10), State as an enum (19), Facade (12), Concurrency: seat holds (25) |
| lld-06 Splitwise | Strategy (16), Facade (12), Value objects (01) |
| lld-07 Vending machine | State (19), Strategy, optional (16), Observer, follow-up (17) |
| lld-08 Pub-sub logger | Chain of Responsibility (21), Observer (17), Strategy: Formatter (16), Decorator: AsyncSink (11), Registry: LogManager.get (06), Producer–consumer (25) |
| lld-09 Snake and ladder | Strategy (16), Observer (17), Dependency injection: seeded dice (04), Value object (01) |
| lld-10 Chess | Inheritance for families (01), Command with undo (18), Iterator (22), One lock for readers too (25) |
| lld-11 ATM | State with guards (19), Adapter: Bank (10), Test double (04), Observer, follow-up (17) |
| lld-12 Library | Strategy: FinePolicy (16), Observer (17), State as an enum (19), Facade (12) |
| lld-13 Hotel | Strategy: pricing rules, cancellation (16), State as an enum (19), Value object: Stay (01) |
| lld-14 Ride sharing | Strategy (16), Decorator: SurgeFare (11), State as a table (19), Facade (12), Compare-and-set (25) |
| lld-15 Food delivery | State as a table (19), Strategy (16), Adapter: PaymentGateway (10), Facade (12) |
| lld-16 Meeting rooms | Strategy: RoomSelector (16), Observer, light (17), Value object: Recurrence (01) |
| lld-17 File system | Composite (13), Facade (12), One re-entrant lock (25), Iterator / Visitor, follow-up (22, 24) |
| lld-18 Text editor | Command (18), Composite: Compound (13), Memento (23), One re-entrant lock (25) |
| lld-19 Auction | State (19), Observer (17), Strategy: IncrementPolicy (16); note that "proxy bidding" is not the Proxy pattern |
| lld-20 Order book | Command queue (18), Single writer (25), Value objects (01) |
| lld-21 Task scheduler | Producer–consumer and worker threads (25), Strategy as a value: RetryPolicy (16) |
| lld-22 Key-value store | Strategy: EvictionPolicy (16), Proxy: read-only view (14), Optimistic concurrency (25) |
Two of its notes are worth a second look. In the auction, "proxy bidding" is the auction term for a bidder who sets a secret maximum that the system bids up to, and it is not the Proxy pattern. And "Strategy / Adapter" in movie booking is one class, SeatLockProvider, seen two ways: as a strategy (the booking logic is identical whichever lock provider it is given) and as an adapter (an in-memory provider for the round, Redis or a database row lock in production, behind one interface).
The order to read them in#
If you read all 22, read them in the course's own order, 01 to 22. If you can't, the program above found the shortest route that meets every built pattern. It starts with the round that has the most patterns and each next round adds the most you haven't met:
| Order | Round | New patterns it adds |
|---|---|---|
| 1 | Pub-sub logger | Singleton and registry, Decorator, Strategy, Observer, Chain of Responsibility, concurrency |
| 2 | Movie booking | Adapter, Facade, State |
| 3 | Chess | Composition and values, Command, Iterator |
| 4 | Parking lot | Dependency injection, Factory |
| 5 | Text editor | Composite, Memento |
| 6 | Key-value store | Proxy |
That is a route, not a verdict on difficulty: the pub-sub logger is a hard round, and the program only counts patterns. Read the other 16 afterwards, in course order, and they will mostly show you patterns you have already met in a new setting.
When you want one pattern rather than the whole map, start with the round that shows it best:
| Pattern | Start here | Then |
|---|---|---|
| Strategy | Parking lot: PricingStrategy, SpotAssignmentStrategy |
Splitwise, hotel, library |
| State | Vending machine: a class per state | Ride sharing and food delivery (transition tables), then the enums |
| Observer | Pub-sub logger: LogBus |
Auction, snake and ladder, elevator |
| Command | Chess: Move and Undo |
Text editor, order book |
| Concurrency | Pub-sub logger: a queue that drops, never blocks | Parking lot, movie booking, ride sharing, order book, task scheduler |
Six rounds meet all 17 built patterns: the logger first with six, then movie booking, chess, the parking lot, the text editor and the key-value store.
Patterns the rounds don't use#
Six of the lessons have thin or no support in the 22 rounds. That is not a reason to skip them, but it tells you where an interviewer is more likely to be improvising:
| Lesson | In the rounds | Where it would fit |
|---|---|---|
| Builder | none by name | the hotel's Stay, the meeting rooms' Recurrence |
| Prototype | none by name | the text editor's snapshots are copies; recurring bookings |
| Bridge and Flyweight | none by name | movie booking's seat maps; the editor's characters |
| Template Method | none by name | the shape is in the logger's Processor.handle and chess's Piece.moves; the vending machine's base State gives defaults, which is not a template method |
| Mediator (the other half of dp-23) | none by name | the elevator's controller |
| Visitor | a follow-up in the file system | find and disk usage over the tree |
The right reading is: when a prompt calls for one of these, the case studies won't have rehearsed it for you, and the five questions from the interview lesson matter more than the table.
When the map misleads#
- Counting is not quality. "Strategy in 17 rounds" says Strategy is common, not that a round is better for having it. Several rounds list a pattern as optional or avoided on purpose.
- A filled circle is the round's choice for that prompt. Copying the same patterns into a different prompt is the mistake the previous lesson warns about.
- The table is the rounds' own claims. Each pattern entry comes from the round's patterns table, code or follow-ups, and it is only as precise as those are. Check a row against its case study before leaning on it.
- Patterns without a lesson are missing. Idempotency keys, lazy deletion and topological sort are real tools in the rounds and are not on this map.
In the interview#
Use the map before the interview, not in it: pick the rounds that cover the patterns you are weakest on, and read those closely. In the room, say the choice with its reason in one breath: "This prompt has a rule that varies, so I'll start with a strategy; I'm not adding a state machine unless the objects have a lifecycle." The map tells you what is common, but the sentence has to come from the prompt.
Then expect the follow-ups:
- "Which patterns do you use most?" Strategy first, then State (usually as an enum or a table), then Observer for events and Facade as the front door. Say that State is mostly not the class-per-state form, and why.
- "Which would you not use here?" Name one the prompt invites and the design doesn't need. The vending machine's optional Strategy for change-making is the model: say it, don't build it.
- "What about a pattern you didn't practise?" Fall back on the questions: what varies, what stacks on top, what has a lifecycle, who listens, what's undoable. The patterns outside the map are answers to the same questions.
- "Where would you put Visitor?" Over a stable class hierarchy, as in the file system's
findand disk usage, and say that it is the one pattern the rounds only mention as a follow-up.
Common mistakes#
- Treating the table as a checklist. Adding a Strategy because "17 of 22 have one", when nothing in the prompt varies.
- Rehearsing only the common patterns. Visitor, Mediator and Template Method are rare in the rounds and still come up in a follow-up.
- Reading the rounds in an order that repeats. Three rounds in a row that show the same pattern teach less than the six-round route.
- Counting Observer or State as the full pattern. Most rounds use a light form; know the full one from its own lesson.
- Quoting the map as evidence for a design. "The case studies do it" is not a reason; "this requirement varies" is.
Adding a Strategy because 17 of 22 rounds have one is a mistake: the map follows each prompt, and five rounds have no Strategy at all.
The pattern map
Q1/4 Which pattern appears in the most of the 22 rounds?
Answer & explanation
Answer: B
Strategy is in 17 of the 22 rounds, 16 of them as a pattern the round builds. Observer is in 10 but built in 5, and State is in 9. Singleton is "avoided" in the parking lot, and appears only as a registry, in the logger and the rate limiter.
Q2/4 Chess, the ATM, the file system, the text editor and the order book have no Strategy. What does that show?
Answer & explanation
Answer: C
Each of the five uses other patterns: commands, composites, adapters, a single writer. The map shows that patterns come from what the prompt varies, not from a template.
Q3/4 Visitor is on the map only as a follow-up in the file system. What is the sensible way to use that?
Answer & explanation
Answer: B
A rare pattern can still be a follow-up question. Rehearse the common ones first, and keep Visitor for when the prompt has a stable hierarchy and a growing list of operations.
Q4/4 You can read only six rounds. Which first, and why?
Answer & explanation
Answer: B
The program picks the round that adds the most unseen patterns each time. The logger builds six, then movie booking adds three more, and so on until all 17 built patterns are covered.
this lesson in shorts · 3
play them all →One idea from this lesson in about a minute each, made for your phone.
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.


