Example-Driven Thinking
"Design the inventory system" cannot be reasoned about; "three units, Alice buys two, Bob buys two at the same moment — what should happen?" can. A concrete example turns a design task into a question with an answer, and the answer usually reveals the requirement the abstract sentence was hiding.
The situation, the reflex, and why it stalls
Every lesson starts where being stuck starts: someone has a problem, and the first move that comes to mind feels like progress.
The problem is stated abstractly and you cannot get a grip on it. How do you construct a concrete example that makes the real question visible?
The ticket says "design the inventory system for the store". You have opened a document titled Inventory Design, written the heading Requirements, and stopped. Everything you can think of is either obvious — stock goes down when someone buys — or too vague to write down. An hour in, the document has three headings and no sentences.
Look for the general solution. Search "inventory system design", read about reservation patterns and event sourcing, and try to fit the store into one of them. It feels like the right level: the ticket was abstract, so the answer should be too, and a well-known pattern looks like an answer.
The pattern is chosen before the problem is known. Reservation-with-expiry is an answer to a question — "what happens between adding to cart and paying?" — that nobody has asked about this store yet, so it cannot be judged right or wrong, only familiar.
- The pattern is chosen before the problem is known. Reservation-with-expiry is an answer to a question — "what happens between adding to cart and paying?" — that nobody has asked about this store yet, so it cannot be judged right or wrong, only familiar.
- The document stays abstract because the thinking is. "Stock must be accurate" is true and useless: it does not say what accurate means when two people are buying, or whether the cart holds stock, or what the admin sees while a payment is pending.
- The hard case is discovered in production. Two customers buy the last unit, both orders are created, one is cancelled by hand, and the design document is updated afterwards to describe what the code accidentally does.
- Every question the founder asks is answered with "it depends" — which is honest, but the design never says on what.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- When an abstract problem will not yield, stop trying to solve it in general and construct one specific case: named actors, specific numbers, a specific sequence of actions, and a specific moment. "Product A has three units. Alice puts two in her cart. Bob puts two in his cart. Both press Pay at the same moment." Then ask the only question a concrete case can be asked: what *should* happen?
- Answer it as the business would, not as the database would. "One of them gets the units and the other is told sorry" is a business answer; "the second update wins" is not an answer, it is a description of an accident. The gap between the two is the requirement.
- Vary one thing at a time and ask again. Alice pays first, then Bob: obvious. Both at once: the race. Alice adds to cart but never pays: does her cart hold stock, and for how long? Each variation is a new example, and each one either has an obvious answer — good, write it down — or does not, which means you have found a decision.
- Keep the example small enough to hold in your head and specific enough to trace through the code you have not written yet. Three units and two people is enough; a warehouse network is not an example, it is another abstraction.
The same problem, stated two ways
The abstract statement and the example below describe the same store. Only one of them can be traced, argued with, and turned into a test. The engineering reason to prefer the example is not that it is friendlier; it is that it has a state at every step, and a state is something you can be wrong about.
Design the inventory system. Stock must be accurate and customers must not be able to buy products that are out of stock.
Product A has three units. Alice puts two in her cart; Bob puts two in his. Both press Pay within the same few milliseconds. What should happen — and what is the stock count afterwards?
The abstract version is satisfied by a system that checks stock before each order and is wrong under concurrency. The example forces the answer "exactly one of them gets the units" and, in the same breath, the question "where is that enforced?" — which is the entire design problem, and was invisible in the ticket.
Tracing the example through code that does not exist yet
Once the example is written, trace it through the most obvious implementation — the one a first draft would produce — and watch the state. The trace below is the whole reason the lesson exists: three lines of pseudocode and one interleaving produce a bug that the design document could not have contained, because the document had no clock.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Alice and Bob pay at the same moment | Two orders, stock reads one, more units sold than existed | Check and decrement are separate operations with a gap between them | Make check-and-decrement atomic, or enforce stock ≥ 0 where the write happens; the example becomes the test |
| Alice adds two to her cart and walks away | Either Bob is refused for units nobody is buying, or carts mean nothing | Nobody decided whether a cart holds stock | Decide, write it down as an assumption, and note what a reservation-with-expiry would cost if the answer changes |
| Alice's payment times out after stock was decremented | Stock is one, no paid order exists, Bob was refused for nothing | Decrement-then-pay has a failure path that pay-then-decrement does not, and vice versa | Pick the order deliberately and design the compensation for its failure case |
1place_order(customer, product, qty):2 stock = read_stock(product) # Alice: 3 Bob: 33 if stock < qty: refuse("only " + stock + " left")4 create_order(customer, product, qty)5 write_stock(product, stock - qty) # Alice: 1 Bob: 16 7# Interleaving:8# Alice reads 3 Bob reads 39# Alice checks ok Bob checks ok10# Alice writes 1 Bob writes 111# Result: two orders for two units each, stock shows 1. Four sold of three.The read and the write are separate steps, and the check lives between them. Nothing in the abstract requirement said "check and decrement must be one step"; the example says it in four lines. Where that step lives — a constraint, an atomic update, a lock, a reservation — is the handoff to the database and concurrency domains.
From the example to a question worth researching
The example does not just find requirements; it sharpens the research question. Before it, the unknown was "inventory". After it, the unknown is small enough to answer with a database and an afternoon — and the ladder shows the same need at three levels of usefulness.
why The best form names the failing interleaving, the invariant, and the candidate mechanisms, so it can be answered by an experiment against a real table rather than by reading about inventory in general. The vague form can only be answered with an architecture.
How to do it
Most important first.
- Write the example with proper nouns and small integers. "A customer" cannot be traced; "Alice" can. Three units, not "some stock".
- Put a clock in it. Most interesting examples involve two things happening close together, and "at the same moment" is the phrase that exposes concurrency, ordering and ownership questions (Invariants Under Concurrency).
- Ask "what should happen?" before "what would the code do?". The first is a requirement; the second is a prediction about a system that does not exist yet.
- Trace the example through each state the data passes through: stock before, after Alice, after Bob, after the failed one is refused. If you cannot say what the number is at each step, that step is a decision you have not made (What Information Changes Over Time?).
- Turn each decided example into a test description now, before code. "Given three units and two simultaneous orders of two, exactly one succeeds and stock is one" is a sentence a test can be written from (Invariants as Tests).
- When the example produces a question you cannot answer from the business side — is oversell ever acceptable? — that question goes to the founder, in the example's words, not in yours (Asking People).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Abstract: "design inventory". Example: product A, three units. Alice adds two, Bob adds two. Nothing has changed — carts do not touch stock, and the example makes that a decision rather than an assumption: should they? For V1, no, and it is written down.
- Alice pays at 10:00:00.000. Bob pays at 10:00:00.010. Business answer: Alice gets two, Bob is refused with "only one left". Now the design has a requirement — stock must never go below zero — and a question the example forced: where is that decided, and what does the code have to do so that Bob's order, which was already validated against three units, is refused?
- The trace: stock 3 → Alice checks (3 ≥ 2, fine) → Bob checks (3 ≥ 2, fine) → Alice writes 1 → Bob writes 1. Two orders, stock reads 1, four units sold out of three. The abstract document never contained this; the example produced it in one line, and the line is the reason to read about atomic decrements and constraints before writing checkout.
- One more variation: Alice's payment provider times out after stock was decremented. Business answer: the units come back. Which means "decrement, then pay" and "pay, then decrement" are different designs with different failure cases, and the example has turned a heading into a decision with two options and a trade-off.
How you know it worked
What now exists that did not before, and what question you can now ask.
- The design document has sentences in it, and the sentences have names and numbers.
- You can state at least one requirement that was not in the ticket — "stock never goes below zero", "a failed payment returns the units" — and say which example produced it.
- The question that stopped you has become one you can research: not "how do inventory systems work?" but "how do I make check-and-decrement atomic in this database?"
- Several examples have obvious answers and one or two do not, and the ones that do not are written down as decisions for someone.
The questions you can now ask
The field this whole domain exists for. After this lesson, these are the questions to put to an unfamiliar problem.
- ?What is one specific case — names, numbers, a sequence, a moment — of the abstract thing I have been asked to design?
- ?In that case, what should happen, in the business's words?
- ?What would the obvious code do in that case, and where does it differ from what should happen?
- ?What one variable can I change to produce the next example — timing, order, quantity, failure?
- ?Which examples have obvious answers, and which have exposed a decision nobody has made?
What can go wrong
- The example is too big to trace. Five products, three warehouses, a returns flow — nobody can hold it, so the trace is skipped and the example becomes decoration on the same abstract document.
- The example is answered from the code's point of view. "What should happen?" gets "the second write overwrites the first", which is a bug described as a behaviour. The business answer comes first; the code answer is what you compare it to.
- One example is taken as the whole requirement. Alice-and-Bob shows the race; it says nothing about carts holding stock, about the admin correcting a count, or about returns. Examples are generated, varied and collected, not found once.
- Examples are used to avoid ever generalising. After twenty of them the design is a list of cases with no rule behind it; the point of examples is to *find* the rule — stock never goes negative — and then let the rule cover the cases.
- A concrete example is narrower than the problem. Everything it reveals is about that case; a rule still has to be extracted and defended.
- Constructing examples is slow at first — a page of Alices and Bobs to reach one sentence of requirement — and to an observer it looks like storytelling rather than design.
- Examples expose decisions that then have to be made, often by someone else. An abstract design could have shipped without asking whether oversell is acceptable; the example cannot.
- "So examples replace requirements." No — they produce them. "Stock never goes below zero" is the requirement; Alice and Bob are how it was found and how it will be tested.
- "This only matters for concurrency." The move is general; concurrency is just where it pays fastest, because "at the same moment" is the variation most people forget to try. A pricing example — a price changes between adding to cart and paying — works the same way.
- "A senior engineer would have known the race was there." Probably — because they have a stock of examples from previous systems and ran this one in their head. The move is how that stock is built.
Where this applies
Problem-solving advice is stated as universal far more often than it is. These labels say what each method is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- GENERALConstructing a concrete case to interrogate an abstract statement works for requirements, algorithms, data models and APIs alike; what changes is what "the moment" is — a request, a call, a row — not the move.
- ILLUSTRATIVEProduct A, the three units, Alice, Bob and the millisecond timestamps are invented to show the shape of the move; the numbers are for tracing, not for any real inventory.
- DOMAIN-SPECIFICIn a payments or inventory domain the productive variation is timing and failure; in a search or recommendation domain it is input shape — empty query, one character, unicode — and the business answer is often "whatever is least surprising" rather than a hard rule.
Where the depth lives
This domain asks the question and hands the answer off by name.