ChecksGENERALSCALE-SPECIFICILLUSTRATIVE

Understanding Is Demonstrated by Modification

Five new requirements — a maximum of 5 per product, several carts per user, a cart that survives reload, an anonymous cart merged at login, inventory checked on add. Each breaks a named assumption and touches a named set of artefacts. If you can say which before touching the code, you understood the cart; if you can only find out by breaking it, you received it.

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 question

A requirement lands on your working cart. Before editing anything, can you name the assumption it breaks and every artefact it touches — rules, examples, lines, tests, persistence, API — and what does it mean if the list is a guess?

The situation

The cart works. The product owner says "a shopper cannot add more than five of anything". You open addItem and start typing an if statement, and then realise you do not know whether changeQuantity needs the same check, whether there is an example for it, or where the number five should live.

The reflex

Add the if. It is one line in addItem, the requirement said "add", and the tests still pass — which they would, since none of them adds six of anything. Ship it, and handle changeQuantity when someone notices.

Why it stalls

The tests pass because the examples do not include the new edge. A modification whose tests still pass unchanged has not been tested; it has been left alone by a suite that predates it.

What the reflex produces — and fails to produce
  • The tests pass because the examples do not include the new edge. A modification whose tests still pass unchanged has not been tested; it has been left alone by a suite that predates it.
  • The assumption that broke was "any positive quantity is valid", and it was encoded in two operations and one rule. The if fixed one encoding. Someone will set the quantity to 20 through changeQuantity, and the rule will be found to have two homes, one of them empty.
  • The number five is in the code and nowhere else — not in a rule, not in an example, not in a test name. The next requirement ("five, except for bulk items") has nothing to attach to.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

Precisely enough to apply it to a problem you have never seen — not a slogan.

  • Before any edit, write the requirement as a change to the concept, not to the code: which rule is new or altered, which examples are new (an edge at the limit, an invalid case past it), which operations enforce it, which tests come from the new examples, and whether state, persistence or the API move. The concept's modifications record does exactly this for five requirements, and the list of what each one touches is the answer key.
  • Name the assumption the requirement breaks. Maximum quantity breaks "any positive quantity is valid". Several carts break "one cart per owner". Survive reload breaks "the cart only exists while the page does". Merge at login breaks "one owner for the cart's whole life". Inventory breaks "the cart is the only authority on what can be added". The assumption tells you how far the change reaches: a rule change reaches operations and tests; an owner change reaches state, every operation's signature, the API paths and persistence (When Assumptions Change).
  • Then edit in the loop's order: rule, examples, flowchart placement, pseudocode, code, tests. The if is the fifth step, and by then it is obvious where it goes — after the find, because the check needs the existing quantity — and that changeQuantity needs it too.
  • Judge the modification by whether the understanding check still passes afterwards. Question six of the check is this lesson; if the answers to questions two and five did not change when the cart moved to a database, the persistence modification was pasted (Six Questions the Working Cart Cannot Answer for You).

Rule → validation → code, for the new rule

The maximum-quantity requirement, run through the rule device before any code is touched. The validation line is where the placement decision lives — "after the find" — and the code is what that sentence becomes. Both operations that enforce it are in the pseudocode, because the rule has two homes and the device makes that visible.

Maximum quantity per product is 5

rule No entry in the cart has a quantity greater than 5 — the assumption "any positive quantity is valid" is withdrawn.

becomes validation In addItem, after finding the existing entry, reject if existing quantity plus the added quantity exceeds 5 (or the added quantity alone, when there is no entry). In changeQuantity, reject if the new quantity exceeds 5. The limit is named once and read by both.

becomes code
MAX_PER_PRODUCT = 5

-- in addItem, after the find:
newQuantity = (existing ? existing.quantity : 0) + quantity
if newQuantity > MAX_PER_PRODUCT: reject "at most 5 per product"

-- in changeQuantity, after the zero case:
if quantity > MAX_PER_PRODUCT: reject "at most 5 per product"

The edge, seen as a state change

The two new examples, as before / operation / after. The edge case at the limit succeeds and the changed list has one line; the invalid case past it leaves the state untouched and the changed list says so. Both lists become assertions, and the second is the one an off-by-one would break.

The invalid case past the limit
before
[ { productId: "laptop", quantity: 5 } ]
addItem(cart, "laptop") → rejected: "at most 5 per product" →
after
[ { productId: "laptop", quantity: 5 } ]
what changed nothing — items[0].quantity is still 5, and the test asserts it. · the reject happened after the find and before the increase; the find had to run, because the check needs the existing quantity — which is why the diamond sits after it.

Five modifications, five reaches

The matrix quotes the concept's modifications — the assumption each one breaks and what it touches — and adds the column that predicts the size of the work: whether the change is a rule, an operation, or a signature. Reading down the last column is the lesson: the same word, "add", lands on five very different amounts of cart.

Change (from the concept)BreaksTouchesKind of reach
Maximum quantity per product is 5.any positive quantity is validrules; examples (edge at 5, invalid at 6); addItem and changeQuantity; testsa rule — two operations, four tests
A user can have several named carts.one cart per ownerstate (cart id and name); every operation takes a cart id; API paths; persistenceevery signature — wide and shallow
The cart must survive a reload.the cart only exists while the page doespersistence (browser storage); load on start; the stale-product failure modea wrapper — no operation changes, one new failure mode
An anonymous cart merges into the user's cart at login.one owner for the cart's whole lifea merge operation with a rule for overlapping products; examples for the overlap; API (login hook); testsa new operation with a decision the requirement did not make
Adding checks inventory.the cart is the only authority on what can be addedrules (stock); addItem asks inventory; the race between two shoppers and the last unit; failure modesa rule that lives elsewhere — and a race

The implementation ladder

Concept, examples, pseudocode, code, tests, production — for the concept this lesson is about. Code is the fourth tab, not the first.

concept Shopping Cart beginner
Build it step by step →

Shopping Cart = A temporary collection of products the user intends to purchase, held between browsing and checkout.

Identity, ownership, lifetime
  • 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.
State it must remember
  • 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.
Operations
  • 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
Rules that must always hold
  • 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.

  • Take one modification from the concept's five and, with the code closed, write: assumption broken, rules touched, examples added, operations touched, tests added, anything in state / persistence / API. Then open the record and compare.
  • Write the new examples first, always. Maximum 5: [ Laptop × 4 ] + add Laptop → [ Laptop × 5 ] (edge); [ Laptop × 5 ] + add Laptop → rejected, cart unchanged (invalid). The tests come from these (Normal, Edge, Invalid).
  • Place the new diamond on the flowchart before the new line in the code; the placement decides which operations get it and what must be looked up first (Draw the Branches Before You Trust Them).
  • Put the number in one place with a name — a rule, a constant — and reference it from both operations, so the next requirement changes one thing.
  • Do the reload modification in the lab and watch which answers in the understanding check move: what breaks if persistence appears is now a real question with a stale-product answer.

Worked on a concrete problem

The move has to produce something. This is what it produced.

  • Maximum 5 per product. Assumption broken: any positive quantity is valid. Rule: quantity per product ≤ 5. Examples: edge at 5, invalid at 6 — for add on an existing entry (4 + 1, 5 + 1) and for change (set to 5, set to 6). Operations: addItem, after the find, on existing + quantity; changeQuantity, on the new quantity. Tests: four, from the four examples. State, persistence, API: untouched, except that the API's 400 now has a new wording. The concept's record lists rules, examples, the two operations and tests — the same four.
  • Several named carts per user. Assumption broken: one cart per owner. State: a cart id and a name; the owner now has many. Operations: every one takes a cart id — the signature of all five changes. API: /cart becomes /carts/:id. Persistence: the cart table needs the name and the owner index. Rules: none new — the per-cart rules are unchanged. The reach is wide and shallow: many files, no new logic. A learner who "adds a carts array" has done the state and missed the signatures.
  • Anonymous cart merges at login. Assumption broken: one owner for the cart's whole life. A new operation, merge(anonymous, owned), with a rule for overlapping products — sum the quantities, or keep the owned one, a decision the requirement did not make. Examples for the overlap: [ Laptop × 1 ] merged into [ Laptop × 2 ] → [ Laptop × 3 ] under the sum rule. API: a hook at login. Tests from the examples. This one adds an operation; the maximum-quantity one added a rule; the difference in reach is the difference in the assumption.
  • Inventory checked on add. Assumption broken: the cart is the only authority on what can be added. Rule: quantity ≤ available stock — enforced by asking the inventory service, at add and again at checkout. The new failure mode: two shoppers and the last unit, a race the cart cannot resolve alone; the concept's rule says so, and the concurrency and database links are where the resolution lives.

How you know it worked

What now exists that did not before, and what question you can now ask.

  • The list of touched artefacts was written before the code was opened and matched the concept's record, or differed for a reason you can state.
  • New examples exist at the edge and past it, and the tests were derived from them, not from the code.
  • The number lives in one named place, and both operations that enforce it reference it.
  • The reach of each modification — a line, an operation, every signature — was predicted from the assumption it broke.

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.

Next questions
  • ?Which assumption does this requirement break, and how far does that assumption reach — a rule, an operation, every signature?
  • ?What are the new examples at the edge and past it, before any line is written?
  • ?Which operations enforce the new rule, and where in each one does the check go — what must be looked up first?
  • ?After the change, which answers in the understanding check moved, and should they have?

What can go wrong

How the move itself fails
  • Editing the code first and discovering the reach by breakage. The if goes in, the tests stay green, and the second operation is found by a bug report.
  • Adding a rule without an example. A rule with no edge example has no test at the boundary, and the boundary — 5 allowed, 6 rejected — is exactly where an off-by-one lives.
  • Treating every modification as the same size. Maximum quantity is one rule; several carts is every signature. Predicting the reach from the assumption is what stops the second being estimated like the first.
  • Over-generalising early. "Five, except for bulk items" has not been asked for; a rules engine for per-product limits is a forecast, and the concept's versions say what arrives when.
What the move costs
  • Writing the touch list before the edit is slower for a one-line rule; it is the same minute that finds the second operation the one line missed.
  • A named constant for a number used twice is a small indirection for a small gain; it becomes worth it at the third use or the first exception.
  • Predicting reach from assumptions is a judgement and it will be wrong sometimes — the record is a guide for five modifications, and the sixth is yours to reason about.
Misreads
  • "So a modification is a checklist: rule, example, code, test." It is the loop's order applied to a change; the checklist form is the shape, and the content — which rule, which example, which operation — is the understanding. Two people can follow the checklist and only one of them finds changeQuantity.
  • "If the tests pass, the modification is done." The tests that exist predate the requirement; they cannot fail on it. The tests that would fail are the ones derived from the new examples, and until they exist the modification is unverified.
  • "Understanding means I can make any change quickly." It means the reach of a change can be predicted before it is made. A wide change is still slow; it is no longer a surprise.

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.

  • GENERALNaming the broken assumption and listing the touched artefacts before editing applies to any change to any concept; the five modifications here are the cart's, and every concept in the catalog carries its own set.
  • SCALE-SPECIFICThe inventory modification is the one whose reach depends on scale: one server and a small store enforce it at checkout and accept the race; a store where the last unit matters resolves it with the database and the concurrency lessons, and the cart's rule says where.
  • ILLUSTRATIVEThe maximum of 5, Laptop × 4 → Laptop × 5, and the merge sum are for the shape of the argument; the five modifications and what each touches are quoted from the concept record.

Where the depth lives

This domain asks the question and hands the answer off by name.