Implementation Ladder
The ten levels every concept climbs, the cart's V0–V6 with the reason each stage exists, the persistence decision, where the code should live, and the representation from UI state to rows.
"Make it production-ready" is a slogan. The falsifiable version is a ladder: each level exists because of a requirement the level below cannot meet, and climbing a level without that requirement is the over-engineering the slogan warns about. A demo cart stops at level 4 and is finished.
The ten levels
impl §26 — every level says why it exists.
- 1Plain-English behaviour — if you cannot say what it does in a sentence, no representation will rescue it; everything below is checked against this sentence.
- 2Examples — before / operation / after — the examples are where the rules are discovered ("add Laptop twice → one entry with quantity 2"), and every test later comes from one of them.
- 3Pseudocode — the algorithm has to be right before any syntax is; pseudocode is the last level that every language shares.
- 4In-memory implementation — one shopper, one process, no persistence — the behaviour has to work where nothing else can go wrong.
- 5Tests from the examples — the examples were the specification; the tests are the specification made executable, so a later level cannot silently break level 1.
- 6Persistence — the application exits and the state disappears; the first real requirement decides whether it should — browser storage, server memory or a database.
- 7API — the moment the state lives outside the browser, the browser needs a way to call the operations — each one becomes an endpoint.
- 8Frontend — the UI holds a copy for display and calls the API for truth; keeping the two from drifting is the whole design of this level.
- 9Failure handling — two tabs, a stale catalog, a restart mid-operation — the failures that do not exist at level 4 exist now, and each needs a decided response.
- 10Production — monitoring, expiry, scale — added only when a reading says so, which is why this is level 10 and not level 1.
The cart, V0 → V6
impl §50–51 — what each version adds, why, and which earlier assumption it breaks.
Cart functions
The persistence decision
impl §23–24 — eight yes/no answers decide the level.
Without an identity, the only handle the server has on this person is a cookie or session id. Anything that must follow the person to another device needs a login first.
what if yes Answering yes to anonymous changes nothing: the level stays In memory. Nothing answered yes needs the state to outlive the page, so a variable in the running program is the whole persistence story.
One device means the browser can be the store; two devices mean something outside the browser has to hold the truth.
what if yes Answering yes to single-device changes nothing: the level stays In memory. Nothing answered yes needs the state to outlive the page, so a variable in the running program is the whole persistence story.
In-memory state lives exactly as long as the page. This is the first question that pushes the concept out of a variable.
what if yes Answering yes to survive-reload moves the level up from In memory to Browser storage. Serialise on every change and load on start — at least browser storage.
Sync needs a place both devices can reach and an identity both devices share; that is a server-side store keyed by a user.
what if yes Answering yes to sync-devices moves the level up from In memory to Database. Database, keyed by the logged-in user.
Anything the client sends can be edited. If money or stock moves on this state, the server must hold the authoritative copy and compute the totals itself.
what if yes Answering yes to server-trusts moves the level up from In memory to Database. The backend owns it; the frontend holds a copy for display.
Shared state needs one place where concurrent changes meet, and rules that hold under concurrency — a database with constraints, not a browser.
what if yes Answering yes to shared moves the level up from In memory to Database. Database, with the invariants enforced as constraints, not just in code.
Server memory is cleared by every deploy and never shared between two servers. This question separates a step from a destination.
what if yes Answering yes to survive-restart changes nothing: the level stays In memory. Nothing answered yes needs the state to outlive the page, so a variable in the running program is the whole persistence story.
A report runs against a database, not against a customer's browser. If anyone other than the owner needs to read it, it needs to be somewhere they can query.
what if yes Answering yes to visible-to-business moves the level up from In memory to Database. Database rows, so that a query can find it after the user has left.
The verdict
8 unanswered — unanswered counts as no, except where the model says the safe reading is yes.
A demo, a test, V0. The cart is a variable in the running page and lives exactly as long as the page does.
- • Nothing answered yes needs the state to outlive the page, so a variable in the running program is the whole persistence story.
cost Nothing — and nothing survives. The whole implementation is the operations and the rules.
where it is taught →- frontendlatencypersistenceauthority
Nothing outside the browser acts on it, so the browser owns it: the rules, the totals and the storage all live in the frontend.
- backendno criterion
No server operation acts on it; backend code here would be a copy of frontend rules with nothing to protect.
- databaseno criterion
No rows yet — the database enforces nothing until the state lives there.
- shared libraryreuse
Pure logic — the total, the quantity rule, the shape of an item — runs identically on both sides; a shared module keeps the frontend estimate and the backend truth from drifting apart.
One cart, four representations
impl §37 — the same state as UI state, API JSON, a domain object and rows; each layer owns something the others must not.
- UI statetaught here →
items: [{ productId: 'p1', quantity: 2, name: 'Mug', price: 1200 }]What the screen needs right now: the items array plus display fields (name, price) copied in so a row can render without a fetch. Optimistic updates happen here first.
- ↓ API JSONtaught here →
{ "items": [{ "productId": "p1", "quantity": 2 }] }The contract between the two sides: only what the server needs to identify the item. No name, no price — the server looks those up, because a price sent by the client is a price the client chose.
- ↓ Backend domain objecttaught here →
class Cart { items: Map<ProductId, CartItem>; addItem(productId, qty); remove(productId); total(catalog) }The behaviour and the rules:
addItemmerges instead of duplicating, quantity ≤ 10, totals computed from the catalog price, not the request. - ↓ Database rowstaught here →
cart(id, owner_id, updated_at) cart_item(cart_id, product_id, quantity, UNIQUE (cart_id, product_id))
Durability and the concurrency-proof copy of one rule: two tabs adding the same product cannot create two rows, whatever the code did.
Implementation is not engineering
impl §52 — the same cart, two different questions.
| Implement — "I can't code it" | Engineer — "I can't make it production-ready" | |
|---|---|---|
| The question | How do I make this behave correctly? | How do I make this survive users, time and other people? |
| Where it runs | One process, one caller, in memory | Two tabs, two servers, a database, a deploy |
| What can fail | A rule was encoded wrong | A race, a restart, a stale copy, a lying client |
| The check | The examples pass as tests | The constraints hold under concurrency; the rules hold on the server |
| Done when | Level 5 — tests from every example | Level 9 — every failure has a decided response; level 10 only when measured |
rule One logical entry per product.
↓ becomes validation Before adding, look for an existing entry with the same product id and increase it instead of appending.
existing = find(cart.items, productId)
if existing: existing.quantity += quantity
else: append(cart.items, { productId, quantity })
-- and, at level 6 with a database:
UNIQUE (cart_id, product_id)The persistence model knows eight answers and four levels; it does not know which concept you are building, so server memory gets the same "a step, not a destination" warning for a rate limiter (where it is fine) as for a cart (where it is a bug report). The placement ranking counts criteria; a team's ownership boundaries can override it.