Case: Implement a Shopping Cart
Start with "implement a shopping cart" and nothing else. Meaning, state, operations, rules, examples, data structure, pseudocode, code, tests, persistence, API, frontend, failures — in that order, each derived from the one before.
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.
You are told "implement a shopping cart" with no further detail and you have never written one. How do you get from that sentence to working, explainable code without receiving the code from somewhere else?
The ticket says "cart". You know what a cart is as a shopper. You do not know what a cart is as code — whether it is a class, an array, a table, or a component — and you have no idea which of those to type first.
Ask an assistant for "a shopping cart in TypeScript" or open a tutorial that builds one. In minutes there is a CartService with methods, a Redux slice, maybe a cart table. It compiles, it renders, and it looks exactly like the thing the ticket asked for.
The pasted cart has a stored total field and a stored productName on every item. Nobody challenged either. When a product is renamed, the cart shows the old name; when a quantity changes and one code path forgets to update the total, the cart lies — and you cannot say which line is at fault because you never decided that the total should be derived.
- The pasted cart has a stored
totalfield and a storedproductNameon every item. Nobody challenged either. When a product is renamed, the cart shows the old name; when a quantity changes and one code path forgets to update the total, the cart lies — and you cannot say which line is at fault because you never decided that the total should be derived. - A class exists with methods, but no one wrote down what "add the same product twice" should do. The tutorial appended a second entry. The review asks "why are there two Laptop rows?" and the honest answer is "that is what the tutorial did".
- There are no examples and therefore no tests, because the examples were never written; the tests that exist test the framework wiring, not the cart. When the first rule arrives — quantity must be positive — there is no place it obviously belongs.
- The reflex produced motion: a service, a slice, a table. It did not produce a reason for the data structure, a list of rules, or a single sentence that says what a cart means. Those are what you need when the requirement changes, and they are the part the reflex skipped.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Refuse to write code until the concept has a meaning in one sentence, a list of what it must remember, a list of what can happen to it, the rules that must always hold, and concrete before/after examples. Each of those is derivable from the previous one; none of them needs a language.
- Only then choose a representation, and choose it by asking what the operations need: how often each runs, what lookup they do, whether order matters, whether duplicates matter, how large it can get. Annotate what each operation costs in the structure you chose (Why This Data Structure?).
- Write each operation in numbered plain English, then in language-neutral pseudocode, then implement one operation in one language. The four-language ladder on this page shows the same algorithm in C++, JavaScript, TypeScript and Python — behaviour stable, syntax changing (The Same Algorithm in Four Languages).
- Turn the examples into tests, each naming the example it came from. Then, and only then, ask the engineering questions: should it survive a reload, what does the browser call, where does the code live, what fails — and answer each only as far as the current version justifies (V0 to V6, With a Reason for Each).
Meaning, state and operations — before a line of code
The exemplar concept record opens with the sentence "a temporary collection of products the user intends to purchase, held between browsing and checkout", and five identity questions: does it have identity, who owns it, how long does it exist, should it survive reload, should it survive login. None of them has a code answer; every one of them shapes the code.
The state canvas then lists nine candidate fields and challenges six of them. The board below shows what was known after that challenge and what was still open — each unknown as a specific question with an experiment, because "should the cart persist?" is a feeling until it is "does a reload emptying the cart matter to the person who asked?".
- ✓Meaning: a temporary collection of intended purchases between browsing and checkout.
- ✓Kept fields: items, items[].productId, items[].quantity. Derived: productName, price (for a cart). Dropped: total, createdAt. Depends: owner, currency.
- ✓Operations: add, remove, change quantity, view, total, clear — sorted create / read / update / delete / domain.
- ~One shopper, one process, one currency in V0 — each written down so that the version that breaks it (V1, V3, later) is a decision rather than a surprise.
? Should the cart persist?
becomes Does a reload emptying the cart matter to whoever asked for it — and if so, on one device or across devices?
experiment Ask. If the answer is "one device", browser storage is the whole answer and the server never sees the cart; if "across devices", the cart needs an owner and rows.
? Does the cart need the price?
becomes Should the shopper see the current price or the price at the time of adding — and which of those does an order need?
experiment Write the example "price changes while the cart is open" for both cart and order; the two answers differ, and the difference is why a cart derives and an order snapshots.
? What about stock?
becomes Is the cart allowed to hold more than the inventory has, and who decides at checkout?
experiment Write the rule "quantity cannot exceed stock" with
whereElse: inventory; defer enforcing it to V5 and note that the cart alone cannot win the race for the last unit.
Nothing on this board is a technology. The operations are verbs, the fields are nouns, and the unknowns are questions with experiments — the deliverable of the first hour of a concept.
Rules and examples — where the implementation comes from
The rule that makes a cart a cart is "one logical entry per product", and it was not invented; it was discovered by writing the example "[Laptop × 1] → add Laptop" and asking what the after should be. Two entries is what a naive append produces. One entry with quantity 2 is what the shopper means. The rule follows, and from the rule the validation, and from the validation the check.
The state change below is that example; the rule device is the encoding. Read them in this order — the code is the last line, not the first — because a learner who sees find before they see the example will remember the method and forget why it exists.
- Every quantity is greater than zero → reject an add or change whose quantity is ≤ 0; turn a change to 0 into a removal.
- The total is never negative → follows from positive quantities and non-negative prices; the check belongs on the price, in the catalog, not in the cart.
- An unknown product cannot be added → check the catalog before touching the items; the API layer repeats the check because it cannot trust the browser.
- Quantity cannot exceed stock → written down now, enforced in V5, and owned by inventory rather than the cart (Rules That Live Elsewhere).
[ Laptop × 1 ]
[ Laptop × 2 ]
rule A product appears at most once in the cart; adding it again increases the quantity.
↓ becomes validation Before appending, look for an existing entry with the same product id and increase it instead.
existing = find(cart.items, productId)
if existing: existing.quantity += quantity
else: append(cart.items, { productId, quantity })Representation, pseudocode, one operation — then the trace
With six operations and four rules in hand, the representation question has content: add needs to find an existing entry, view needs insertion order, the cart holds a handful of items. An array gives order for free and an O(n) scan that is invisible at n = 4; a map gives O(1) average lookup and structural uniqueness at the cost of serialisation and language-dependent order. The exemplar chooses the array and says so (Array Cart vs Map Cart).
The pseudocode is the plain-English steps made precise, and the trace is the pseudocode run on the add-again example — input, lookup, branch, mutation, output. The branch is the rule; if the trace skipped it, you could not tell this addItem from the buggy one that always appends (Predict the Bug).
- inputcart = [ Laptop × 1 ], productId = laptop, quantity = 1
- lookupcatalog.has(laptop) → true; scan items for productId == laptop → found at index 0
- branchitem exists → take the "increase" branch, not the "append" branch
- mutationitems[0].quantity = 1 + 1 = 2
- outputthe cart: [ Laptop × 2 ] — one entry, as the example predicted
- V0 — cart functionsThe five functions over a plain data structure, in memory, with the rules enforced; three tests named after examples. — The behaviour has to be right before anything wraps it — this lesson ends here.
- V1 — cart object with an ownerSeveral carts at once, a catalog to price from. — Two shoppers is the first requirement that makes "which cart?" a question.
- V2 — browser storageSerialise on every change, load on start. — A reload emptying the cart is the first thing a real shopper notices (The Cart Disappears).
- V3 — server rows and an APIGET /cart, POST /cart/items, PATCH and DELETE per product; a unique (cart_id, product_id) constraint. — The cart must be trusted for checkout and seen from more than one device; the constraint is the rule's last line of defence against a race (From Cart.addItem() to POST /cart/items).
- V5 — inventory validationThe stock rule, asked of inventory at add and again at checkout. — Selling what is not there is worse than an empty cart — and the cart alone cannot win the race for the last unit.
1function addItem(cart, productId, quantity = 1):2 if not catalog.has(productId): reject "unknown product"3 if quantity <= 0: reject "quantity must be positive"4 item = find entry in cart.items with item.productId == productId5 if item exists:6 item.quantity = item.quantity + quantity7 else:8 append { productId, quantity } to cart.items9 return cartTwo guards, one lookup, one branch, one mutation, one return. Every line maps to a rule or an example; there is no line here that a reviewer could ask "why?" about without an answer.
The implementation ladder
Concept, examples, pseudocode, code, tests, production — for the concept this lesson is about. Code is the fourth tab, not the first.
Shopping Cart = A temporary collection of products the user intends to purchase, held between browsing and checkout.
- Does a cart have identity? Yes, weakly. Two carts with the same items are still two carts, because each belongs to someone and will become a different order. It needs an id once it leaves memory; in memory the variable is the identity.
- Who owns it? A shopper — a logged-in user or an anonymous session. The owner is part of the state because "my cart" has to be findable again.
- How long does it exist? From the first add until checkout or abandonment. Whether it survives a reload, a closed browser or a login is not a property of the concept; it is a persistence decision made later, and each answer changes where the cart lives.
- Should it survive reload? Usually yes for a store, usually no for a demo. V1 in memory says no; V2 browser storage says yes on one device; V3 server storage says yes everywhere the user is logged in.
- Should it survive login? Only if an anonymous cart and a logged-in cart are merged — a rule that does not exist in V1 and appears as a modification later.
- itemscollection of CartItemkeepThe cart is its items; without them nothing else means anything.
- items[].productIdidkeepThe reference to what is being bought. The catalog owns the product; the cart only points at it.
- items[].quantityinteger > 0keepTwo laptops is one entry with quantity 2, not two entries — the rule "one entry per product" needs a quantity to hold.
- owneruser id or session iddependsSo the cart can be found again by the person it belongs to.
- items[].productNamestringderiveIt would be convenient to render the cart without a catalog lookup.
- items[].pricemoneydependsThe total needs a price per item.
- totalmoneydropEvery screen shows the total.
- currencycodedependsPrices need a currency to be added.
- createdAttimestampdropAbandoned carts might be expired or emailed about.
- update Add item — the updated cart
- delete Remove item — the updated cart
- update Change quantity — the updated cart
- read View items — the list of entries — product id and quantity — for rendering
- domain Calculate total — the sum of price × quantity over the entries
- delete Clear cart — the empty cart
- • Every quantity is greater than zero.
- • One logical entry per product.
- • The total is never negative.
- • An unknown product cannot be added.
- • Quantity cannot exceed available stock — if inventory is enforced here.
How to do it
Most important first.
- Write the meaning first: "a temporary collection of products the user intends to purchase, held between browsing and checkout". If a word in that sentence surprises you — "temporary", "intends" — it is telling you something about lifetime and about why a cart is not an order (Define the Concept).
- List candidate fields and challenge every one: items, productId, quantity, owner, productName, price, total, currency, createdAt. Ask of each "does any V1 operation read this, and can it be derived instead?" (Challenging Unnecessary State).
- List operations and sort them: add, remove, change quantity, view, total, clear. For each: inputs, what it reads, what it changes, output, side effects, errors (Operation Contracts).
- Write the rules as sentences, then as validations, then as pseudocode checks: quantity > 0, one entry per product, total never negative, unknown product rejected (From Invariant to Validation).
- Write examples as before → operation → after, including one edge and one invalid case per operation. Predict the after before you run anything (Normal, Edge, Invalid).
- Choose array or map with the operations in front of you, implement addItem, run the examples as tests, and stop. Persistence, API and frontend are the next lesson, not this one (Implement One Operation).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Meaning: a temporary collection of products the user intends to purchase. Identity: weak — two carts with the same items are still two carts because each belongs to someone. Owner: a user or an anonymous session. Lifetime: from the first add until checkout or abandonment; whether it survives a reload is a persistence decision, not a property of the concept.
- State after the challenge: items (keep), items[].productId (keep), items[].quantity (keep), owner (depends — V0 has one cart), productName (derive from the catalog), price (derive for a cart, snapshot for an order), total (drop — a short loop), currency (depends — one currency in V1), createdAt (drop — no V1 operation reads it). Nine candidates, three kept unconditionally.
- Operations: add item (update), remove item (delete), change quantity (update), view items (read), calculate total (domain), clear (delete). Rules: every quantity is greater than zero; one logical entry per product; the total is never negative; an unknown product cannot be added; a fifth rule about stock is written down but deferred to V5.
- Examples: Cart = [] → add Laptop → [Laptop × 1]. [Laptop × 1] → add Laptop → [Laptop × 2], not two entries — this is the example where "one entry per product" was discovered. [Laptop × 2] → change Laptop to 0 → [] because a cart never holds quantity 0. [] → add Laptop × 0 → [] (rejected: quantity must be positive).
- Representation: an array of CartItem, because a cart holds a handful of items and renders in the order they were added; add is an O(n) scan that is invisible at that size, and the array serialises to JSON for free. A map makes uniqueness structural and lookup O(1) average — the right answer to a wishlist with thousands of entries, not to this cart.
- addItem in TypeScript, derived from the pseudocode: check the catalog, reject quantity ≤ 0,
cart.items.find(i => i.productId === productId), increase or push, return the cart. Three tests, each named after an example: add-again, invalid-zero, to-zero. Then the ladder: V0 functions → V1 owner → V2 browser storage → V3 server rows with a unique (cart_id, product_id) constraint → V4 merge at login → V5 inventory → V6 only on measured evidence.
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every field in the cart can be defended by naming the operation that reads it — and the fields you dropped have a sentence saying why.
- You can read a line of addItem and say which rule it encodes; the find before the append is "one entry per product", not a style choice.
- The tests have example names, and a reviewer who asks "why an array?" gets an answer about operation frequency and size, not "it was in the tutorial".
- When the next requirement arrives — maximum quantity five, several named carts, survive a reload — you can say which of state, rules, examples, implementation and tests it touches before you open the editor.
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 this concept in one sentence, before any representation?
- ?Which fields does any V1 operation actually read — and which can be derived or dropped?
- ?What should happen when the same thing is added twice, and which rule does that example reveal?
- ?Which operation is most frequent, what lookup does it need, and what does that cost in the structure I am about to choose?
- ?Which test comes from which example — and which example has no test yet?
- ?Should this survive a reload — and what is the cheapest level of persistence that answers that?
What can go wrong
- The concept phase becomes a design document. Nine fields challenged, six operations specified, and a week later there is no addItem. The phase should take an hour for a cart; when the examples are written, implement one operation.
- The rules are discovered from imagination instead of from examples. "Quantity must be positive" is easy to invent; "one entry per product" only appears when you write the add-again example and ask what the after looks like. Write the examples.
- Persistence, API and frontend are built in the same sitting as the in-memory functions "while we are here". Now the first bug could be in the rules, the serialisation, the route or the component, and the cart cannot be tested without a database.
- The map is chosen because it is "more efficient", for a cart of four items. Complexity is annotated, not asserted; O(1) average lookup against O(n) with n = 4 is not a reason, and the map costs serialisation and order guarantees.
- The derived path is slower to the first line of code than pasting; for a cart you have already built four times it is an hour spent re-deriving what you know.
- A cart without a stored total recomputes it on every render; for a cart that is trivial, for something else it might not be, and the challenge has to be redone per concept.
- Stopping at V0 leaves a cart that vanishes when the tab closes. That is correct for this lesson and unusable for a shopper; the lesson deliberately ends before the shopper is happy.
- "So a cart is an array of items." No — a cart is a temporary collection of intended purchases; the array is one representation, chosen because the operations and size favour it, and the map or the rows are the same cart under different pressures.
- "The tutorial's cart with a stored total is wrong." It is undefended, not wrong. A stored total with a measured reason and a rule that keeps it in sync is a legitimate V6 decision; the failure was that no one asked.
- "Never use AI for the cart." Use it after your derivation exists — as a reviewer of your rules, as a second opinion on array against map, as a source of edge cases you missed. The line is that it must not replace the derivation (AI as Reviewer).
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.
- GENERALThe order — meaning, state, operations, rules, examples, representation, pseudocode, code — applies to any concept with state; the cart is the running example because every reader has used one and it has exactly one non-obvious rule.
- ILLUSTRATIVELaptop × 2, the price of 1000, the nine candidate fields and the V0–V6 ladder are the exemplar concept record's invented values; no real store is being described.
- TEAM-SPECIFICA solo learner should do every step visibly; a senior who has built several carts does the derivation in their head in minutes and only writes down the rules and examples — the artefacts a reviewer will ask for.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The manifesto's "What Are You Delegating?" at /manifesto/delegating: a pasted cart delegates the rules; the derived cart delegates only the syntax.