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.

I need XWhat is X?What must it remember?What can happen to it?What must always hold?ExamplesRepresent the stateWhich structure?Each operationPseudocodeImplement oneTest with examplesEdge casesIntegrate

"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.

Implementation ladder
  1. 1
    Plain-English behaviourif you cannot say what it does in a sentence, no representation will rescue it; everything below is checked against this sentence.
  2. 2
    Examples — before / operation / afterthe examples are where the rules are discovered ("add Laptop twice → one entry with quantity 2"), and every test later comes from one of them.
  3. 3
    Pseudocodethe algorithm has to be right before any syntax is; pseudocode is the last level that every language shares.
  4. 4
    In-memory implementationone shopper, one process, no persistence — the behaviour has to work where nothing else can go wrong.
  5. 5
    Tests from the examplesthe examples were the specification; the tests are the specification made executable, so a later level cannot silently break level 1.
  6. 6
    Persistencethe application exits and the state disappears; the first real requirement decides whether it should — browser storage, server memory or a database.
  7. 7
    APIthe moment the state lives outside the browser, the browser needs a way to call the operations — each one becomes an endpoint.
  8. 8
    Frontendthe UI holds a copy for display and calls the API for truth; keeping the two from drifting is the whole design of this level.
  9. 9
    Failure handlingtwo 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.
  10. 10
    Productionmonitoring, 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.

V0

Cart functions

adds
The five functions over a plain data structure, in memory, with the rules enforced.
because
The behaviour has to be right before anything wraps it.
breaks
Nothing yet; it assumes one shopper and one process.

The persistence decision

impl §23–24 — eight yes/no answers decide the level.

Is the user anonymous — no login?

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.

Is it only ever used on one device?

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.

Must it survive a page reload or a closed tab?

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.

Must it sync across devices — phone and laptop see the same thing?

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.

Does the server need to trust it — does checkout charge from it?

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.

Is it shared with other users — do two people see or change the same thing?

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.

Must it survive a server restart or deploy?

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.

Does the business need to see it — abandoned carts, reports, support?

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.

level
In memory

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.
survives navigating within the single-page app
loses a reload · a closed tab · a second device · the server ever seeing it

cost Nothing — and nothing survives. The whole implementation is the operations and the rules.

where it is taught →
Progression — every level below the verdict is a level you passed through
In memoryBrowser storageServer memoryDatabase
Where the code lives — impl §36–37, ranked by the criteria it satisfies
  1. 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.

  2. backendno criterion

    No server operation acts on it; backend code here would be a copy of frontend rules with nothing to protect.

  3. databaseno criterion

    No rows yet — the database enforces nothing until the state lives there.

  4. 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.

  1. 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.

  2. ↓ 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.

  3. ↓ Backend domain objecttaught here →
    class Cart { items: Map<ProductId, CartItem>; addItem(productId, qty); remove(productId); total(catalog) }

    The behaviour and the rules: addItem merges instead of duplicating, quantity ≤ 10, totals computed from the catalog price, not the request.

  4. ↓ 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 questionHow do I make this behave correctly?How do I make this survive users, time and other people?
Where it runsOne process, one caller, in memoryTwo tabs, two servers, a database, a deploy
What can failA rule was encoded wrongA race, a restart, a stale copy, a lying client
The checkThe examples pass as testsThe constraints hold under concurrency; the rules hold on the server
Done whenLevel 5 — tests from every exampleLevel 9 — every failure has a decided response; level 10 only when measured
The rule that crosses the line — encoded once in code, again as a constraint
one entry per product

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.

becomes code
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)
SIMPLIFIED

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.