Impl. LoopGENERALDOMAIN-SPECIFICILLUSTRATIVE

Implementation Is Not Engineering

"How do I make a cart add items?" is an implementation question. "Where is the cart stored, synchronised, validated, secured, scaled?" is an engineering question. Conflating them is why beginners are stuck at both.

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

You are asked to "build the cart" and the questions in your head range from "how do I add an item" to "what if two tabs add at once". Which of those are the same problem, and which should be answered first?

The situation

You sit down to write addItem and within a minute you are worrying about whether the cart should be in localStorage or in the database, whether the API should be POST or PUT, and what happens when two tabs are open. None of that helps you write the find-and-increase, and the find-and-increase is the thing you do not know how to write.

The reflex

Answer the biggest question first. Decide the database, the endpoint shape and the sync strategy, because they seem to determine everything else, and the small logic can be "filled in" once the architecture is settled.

Why it stalls

The architecture is decided for a cart whose behaviour nobody has written, so the table has a total column and a name column, and the endpoint returns a total the cart cannot yet compute. The engineering decisions were made about an object that does not exist.

What the reflex produces — and fails to produce
  • The architecture is decided for a cart whose behaviour nobody has written, so the table has a total column and a name column, and the endpoint returns a total the cart cannot yet compute. The engineering decisions were made about an object that does not exist.
  • Every implementation question is answered at engineering scale. "Should remove of an absent item be an error?" turns into a debate about HTTP status codes before the in-memory function has been written to try either.
  • Progress is measured by infrastructure — a table, a route, a store — and the cart still cannot add the same item twice correctly. Motion, not progress.
  • Both halves stay unknown, because each was waiting on the other: the logic waits for the storage decision, the storage decision waits for knowing what the logic needs to store.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

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

  • Sort every question into two piles. Implementation: what does the concept do — what it remembers, what can happen to it, what rules hold, what each operation reads and changes. Engineering: where does the concept live and how does it survive — persistence, the API, which process holds it, synchronisation, validation at the boundary, security, scale. The two piles have different inputs: implementation needs the concept; engineering needs the implementation plus the system around it.
  • Answer the implementation pile first, in memory, with no process boundary in the picture. A cart that adds, removes and totals correctly over a plain data structure is complete as an implementation and is the input to every engineering question — the API returns what getItems returns; the table stores what the state list says.
  • Then take the engineering pile one question at a time, each triggered by a requirement rather than by anxiety: "the cart disappears on reload" triggers persistence; "the browser must call addItem" triggers the API; "two tabs race" triggers a constraint. Each answer wraps the working behaviour; none of them changes what addItem does.
  • Keep the boundary visible in the code. The five cart functions know nothing about React, Express or a database — the cart record says so explicitly — and that ignorance is what lets a React hook, an Express route and a repository each call the same addItem.

Two questions that sound alike

The reflex hears one question — "build the cart" — and answers the biggest part of it. The move hears two, and answers the one whose input exists. The pair below is the cart's own case.

The same afternoon, sorted
Engineering first
Create a carts table with id, owner, total; a cart_items table with product_id, name, price, quantity; POST /cart with the whole cart in the body; a sync strategy for tabs. Then "fill in" add.
Implementation first
addItem, removeItem, changeQuantity, getItems, total over { items: [] } in memory, with six examples as tests. Then, when reload matters, serialise it; when checkout must trust it, move it behind POST /cart/items with a unique constraint.

The engineering-first table stores name, price and total — three fields the implementation would have challenged out — and the POST body has no operation to call. Implementation-first hands every engineering step a working behaviour to wrap, and each wrapper is triggered by a real requirement instead of designed for an imagined one.

The engineering questions as a slice

Once the in-memory cart works, the engineering questions are the layers of a slice around it. Read doesNotProve: the slice establishes that the layers connect, and leaves the hard engineering questions exactly where they should be — next.

Add to cart, end to end
A shopper clicks Add on the product page and the badge shows the new count after a reload
  1. FrontendHolds a copy of the cart in component state; the button calls POST /cart/items and replaces the copy with the response.
  2. APIPOST /cart/items with { productId, quantity }; returns the cart, or 400 with the rule that rejected it.
  3. LogicThe same addItem that passed the in-memory tests — unchanged.
  4. Persistencecart(id, owner) and cart_item(cart_id, product_id, quantity) with a unique constraint on (cart_id, product_id).
proves
The layers connect, the rules fire through the API, and the cart survives a reload because the server holds it.
does not prove
What happens when two tabs add Laptop at the same moment, whether the anonymous cart merges at login, or whether stock is checked — each is an engineering question with its own trigger, and the slice deliberately leaves them open.

Which engineering question is next?

Engineering questions are not answered all at once; each is chosen by the requirement that raised it. The decision names the options as the cart record names them and says what each costs.

The cart disappears when the process exits — should it survive?

Which persistence level does the current requirement justify?

In memory

when A demo, a test, or V0 — the behaviour is still being derived.

cost Nothing survives; the implementation questions get answered without noise.

Browser storage

when One device, no login, must survive a reload.

cost Serialise on every change; invisible to the server; a cleared browser loses it; stale products arrive from storage.

Server memory

when A single server holding carts by session — a step, not a destination.

cost Lost on restart; breaks with two servers.

Database

when Logged-in users, several devices, or a cart the business wants to see.

cost A schema, a round trip per operation, and real concurrency between tabs.

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.

  • Write your questions in one column; mark each I (does the concept do this?) or E (where does it live / how does it survive?).
  • Refuse to answer an E question until the I column is implemented and tested in memory (Pure Logic First).
  • Attach every E question to the requirement that raised it: reload, second device, trust at checkout, two tabs (The Cart Disappears, The Persistence Ladder).
  • When an E answer seems to require changing the concept, check whether it really does — it usually adds a wrapper, a constraint or a field with a version number, not a new addItem (Where Should This Code Live?).
  • Read the cart's V0–V6 versions as a list of E questions answered in the order their requirements arrived (V0 to V6, With a Reason for Each).

Worked on a concrete problem

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

  • Implementation pile for the cart: what does add do when the product is already there (increase the quantity); what does change-to-zero do (remove); is remove-of-absent an error (V1 chooses no-op); what is the total of an empty cart (0). All answered with an array in memory and six examples, in an afternoon.
  • Engineering pile, each with its trigger: "the cart is empty after a reload" → browser storage (V2), which introduces a new failure — a stored product that no longer exists in the catalog. "Checkout must trust the cart" → server-side storage and the API (V3): GET /cart, POST /cart/items, PATCH and DELETE /cart/items/:productId, and the unique (cart_id, product_id) constraint. "Two tabs add Laptop at once" → the rule "one entry per product" that lived in code now also lives in the database constraint, because code cannot see the other tab.
  • What the engineering answers did to addItem: nothing. The Express route parses the body, calls addItem, and returns the cart or a 400 with the rule that rejected it. The repository loads the cart, calls addItem, saves. The React hook holds the cart in state and calls the API. The same eight lines survive every one of those wrappers.
  • One E answer that did feed back: "remove of an absent item is a no-op" was an I decision; the API kept it — DELETE returns success — because the domain's choice, not the transport, decides. Had the API forced 404, the change would be in the route, not in removeItem.

How you know it worked

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

  • You can point at any question about the cart and say which pile it is in and what its trigger is.
  • The in-memory cart passes its example tests before a table or a route exists.
  • Every engineering decision names the requirement that forced it; none of them names a fear.
  • The core functions import nothing from a framework, and a second wrapper — a CLI, a test harness — could call them unchanged.

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
  • ?Is this a question about what the concept does, or about where it lives and how it survives?
  • ?Which requirement is forcing this engineering question — and has it actually arrived?
  • ?If I answer this engineering question, does addItem change, or does something new wrap it?
  • ?What would the in-memory version have to prove before any of the engineering is worth doing?

What can go wrong

How the move itself fails
  • The boundary is used to postpone engineering forever. "Persistence is an E question" becomes a reason never to answer it, and the demo cart ships as the store. Engineering questions are deferred until their requirement arrives — and reload arrives on day one of real use.
  • The piles are sorted and the I pile is still answered with engineering reflexes: "should remove-of-absent error?" answered with "what does REST say?" rather than "what does the shopper want?".
  • A concept whose behaviour genuinely depends on its environment — a rate limiter that must be shared across servers — is forced into the in-memory-first shape, and the single-process version teaches a rule that the real one breaks. Know when the E question changes the I answer, and say so.
  • The separation is treated as a sequence of people: "implementers" and "engineers". It is a sequence of questions the same person asks.
What the move costs
  • An in-memory cart is not a shippable cart, and a stakeholder watching sees an afternoon of work that "doesn't save anything".
  • Keeping the core framework-free costs a wrapper per environment — a hook, a route, a repository — where a tutorial would have one file.
  • Some engineering answers do change the behaviour (a stock check adds an error to addItem), and sorting them late means revisiting tests that passed.
Misreads
  • "Engineering questions are advanced; beginners can ignore them." Beginners can *sequence* them. A learner who has written addItem in memory can answer "should it survive reload?" with a real basis; one who has not is guessing at both.
  • "Implementation is the easy part." The implementation pile is where every rule of the concept is decided; engineering mostly moves those decisions to places that can enforce them. Hard rules are hard in memory too.
  • "Separate piles means separate modules and a repository pattern from the start." The separation is in the questions and, at minimum, in which functions import what. Whether it needs a folder structure depends on the size of the system.

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.

  • GENERALWhat-it-does before where-it-lives holds for any concept that has a pure core; the piles are the same for a cart, a rate limiter or a job queue.
  • DOMAIN-SPECIFICFor concepts whose meaning is about the environment — a distributed lock, a cache — the engineering questions are part of the implementation pile, and the in-memory version is a model rather than a first version.
  • ILLUSTRATIVEThe two tabs, the reload and the store's V0–V6 are the cart record's own scenarios, invented for the shape of the argument.

Where the depth lives

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

Concurrencydata-races
Further
  • The manifesto's layers at /manifesto/layers are the engineering pile drawn as a stack; this lesson is the argument for reaching it second.