To CodeGENERALDOMAIN-SPECIFICILLUSTRATIVE

Language-Neutral Pseudocode

The pseudocode for addItem does not mention React, Express, Django or Spring — or TypeScript, or Python. It models the operation's contract: what it takes, what it reads, which way it branches, what it changes, what it returns. That contract is what stays fixed while the language and the framework change around it, and it is the thing a framework will later wrap.

The moveWorked exampleNext questions

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

Your pseudocode has a useState in it. What went wrong, and what is the pseudocode supposed to be neutral *from*?

The situation

You are writing the cart's pseudocode and, because the app is in React, you write "set the cart state to the new items". Or the API is Express and you write "return 400". Or you have used Django and the cart is "a model with a save method". The pseudocode is starting to look like the framework you happen to know.

The reflex

Write the pseudocode in the framework's vocabulary, since that is what the code will be in anyway. setCart([...cart.items, newItem]) is nearly pseudocode already.

Why it stalls

The framework's vocabulary carries the framework's decisions. setCart([...items, newItem]) has decided immutability, has decided that the cart lives in a component, and has silently dropped the find — because the React idiom for "update one item in a list" looks nothing like "look for an existing entry", and the idiom won.

What the reflex produces — and fails to produce
  • The framework's vocabulary carries the framework's decisions. setCart([...items, newItem]) has decided immutability, has decided that the cart lives in a component, and has silently dropped the find — because the React idiom for "update one item in a list" looks nothing like "look for an existing entry", and the idiom won.
  • The operation can no longer be tested without the framework. The rule "one entry per product" now lives inside a hook, and checking it means rendering a component; the six examples that were a few assertions each become a test harness.
  • When the same cart is needed on the server — for V3 — the pseudocode is useless, because it was never about the cart. It was about React holding a cart. The behaviour has to be rediscovered in Express's vocabulary and the two drift.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

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

  • Be clear about what the pseudocode is neutral from: the *language* (no ===, no None, no std::vector) and the *framework* (no state setters, no request objects, no models with save). What it is not neutral from is the concept: the cart, its items, the catalog, the rules. Neutrality means "only the operation's contract is in here" (Operation Contracts).
  • Write the contract explicitly before the lines: inputs (cart, productId, quantity), reads (items, catalog), changes (items), output (the updated cart), errors (unknown product, quantity ≤ 0). The record carries these on every operation. Pseudocode that touches anything outside the contract — a component, a response code, a database — has left the operation and entered the framework.
  • Where the framework's concern is real — the UI must re-render, the API must answer 400 — write it as a separate step *outside* the operation, in words: "the route calls addItem and turns a rejection into a 400". The operation rejects; who turns rejection into a status code is the wrapper's business (Pure Logic First).
  • Check neutrality by translation: could this pseudocode become Python as easily as TypeScript, and be called from a CLI as easily as from a route? If a line only makes sense in one language or one framework, it is that language or framework's decision, and it should move out — to the code, or to the wrapper.

What the pseudocode is neutral from, and what it is not

The decision, for each thing that might appear in a line. Three of the options are excluded; the last one — the concept's own nouns and rules — is what neutral pseudocode is *made of*, and stripping it is the over-application.

Does this belong in the operation's pseudocode?

A language's decision (undefined, None, ===, spread, vector)

when Never in the pseudocode; it arrives with the code, in whichever language the project uses.

cost The pseudocode reads slightly longer than idiomatic code — find … with … instead of .find(i => …).

A framework's concern (state setter, request, response code, model.save)

when Outside the operation, as a sentence that calls it: "the route calls addItem and turns a rejection into 400".

cost The wrapper is a second piece of work with its own decisions; nothing writes itself.

The concept's contract (cart, items, catalog, productId, quantity, reject)

when Always — this is what the pseudocode is made of; stripping it to collection and key loses the cart.

cost The pseudocode is specific to this concept and cannot be reused for a wishlist without being re-derived — which is correct.

The rule stays in the operation, whoever calls it

Neutrality is easiest to see on a rule. "An unknown product cannot be added" is enforced by the operation with a reject; what the caller does with the rejection — a 400, a toast, a printed line — is not the operation's business. The record notes the API repeats the check because it cannot trust the browser; that repetition is the wrapper's, and the operation's line is unchanged by it.

One rule, no framework

rule An unknown product cannot be added.

becomes validation Check the catalog before touching the items and reject with a message; the caller — route, hook or CLI — decides how the rejection is shown.

becomes code
if not catalog.has(productId):
    reject "unknown product"
-- outside the operation, in words:
-- the route calls addItem; a rejection becomes 400 with the message
-- the hook calls addItem; a rejection becomes a visible error, state untouched

A slice that proves the contract holds across wrappers

One operation, three callers, the same eight lines. The slice proves the neutrality was real; what it does not prove is that the wrappers are right — the hook's rollback and the route's status code are their own work.

Vertical slice
addItem called from a test, a route and a hook, unchanged
  1. OperationThe eight neutral lines: catalog check, quantity check, find, branch, mutate or append, return.
  2. TestCalls addItem twice on an in-memory cart and asserts [Laptop × 2] — no framework loaded.
  3. Route (Express)POST /cart/items: parses the body, calls addItem, returns the cart or 400 with the rejection message.
  4. Hook (React)Holds the cart in state, calls addItem on click, replaces the state with the result or shows the rejection.
proves
The operation's contract is independent of its caller: the same lines, the same find, the same rejection in all three; the rule "one entry per product" cannot be lost by an idiom because no idiom is inside the operation.
does not prove
That the route's 400 is the right status for every rejection, that the hook rolls back correctly on a rejected optimistic update, or that the route's repeated catalog check matches the operation's — the wrappers have their own contracts and their own tests.

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.

  • Start with the contract as a header — inputs, reads, changes, output, errors — and refuse any line that references something not in it (Inputs, Outputs and Side Effects).
  • Ban framework nouns from the pseudocode: state setters, requests, responses, models, sessions. When one appears, write down what it was standing in for (a change to items; a rejection) and use that instead.
  • Use a fixed neutral vocabulary — find … with …, if … exists, append, reject — and use it for every operation on every concept, so a reader learns it once (From English to Pseudocode).
  • Test for neutrality by asking whether the same pseudocode could be implemented in a language you do not know; if it could not, find the line that assumes yours.
  • Keep the earlier discovery-stage lessons in mind for *why* pseudocode: Pseudocode Before Code and Pseudocode as a Thinking Tool are about thinking through a problem; this lesson is about a finished operation's contract, which is what those thinking tools eventually produce.

Worked on a concrete problem

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

  • React-flavoured: "set the cart state to the items with the new item appended." Contract check: "cart state" is a component's concern, and "appended" skipped the find. Neutral: item = find entry in cart.items with item.productId == productId; if item exists: item.quantity += quantity; else: append. The React hook will hold the cart and call this; the find survives because it was never the hook's to lose (The UI Holds a Copy).
  • Express-flavoured: "if the product is unknown, return 400." Contract check: 400 is a response code, and the operation has no response. Neutral: if not catalog.has(productId): reject "unknown product". The route does try addItem … catch → 400 with the message. The same pseudocode serves a CLI that prints the message and a test that asserts it (From Cart.addItem() to POST /cart/items).
  • Django-flavoured: "the cart model's add method saves the item." Contract check: save is persistence, and V0 has none. Neutral: the same eight lines, changing cart.items in memory. A repository will later load the cart, call addItem, and save — the operation is unchanged, and the save is a step outside it (The Cart Disappears).

How you know it worked

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

  • The pseudocode has a contract header and no line references anything outside it.
  • A reader who knows neither your language nor your framework could implement it.
  • The framework concerns — re-render, status codes, saving — exist as separate sentences that call the operation, not as lines inside it.

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
  • ?What is this pseudocode neutral from — the language, the framework — and what must it not be neutral from?
  • ?Does every line reference only the contract: inputs, reads, changes, output, errors?
  • ?Which lines would only make sense in one language or one framework, and what were they standing in for?
  • ?Where did a framework concern go — and is it a step that calls the operation, or a line inside it?

What can go wrong

How the move itself fails
  • Neutrality is taken to mean vagueness, and the pseudocode says "update the cart appropriately" — which is neutral from everything including the algorithm.
  • The concept's own vocabulary is stripped along with the framework's: cart.items becomes collection, productId becomes key, and the pseudocode is neutral from the cart too, which is one step too far.
  • A framework concern that is genuinely part of the operation — the inventory reservation as a side effect in V5 — is exiled to the wrapper, and the operation's contract lies about its side effects.
What the move costs
  • Contract-only pseudocode makes the framework integration a second piece of work, and on a tiny app where the hook *is* the app, the separation is overhead.
  • A neutral vocabulary has to be learned by every reader; a team fluent in one framework may read setCart faster than append … to cart.items.
  • Some operations genuinely have framework-shaped side effects — a realtime broadcast, a transaction boundary — and keeping them out of the pseudocode can hide a real part of the contract.
Misreads
  • "Language-neutral means avoid TypeScript." It means avoid any one language's decisions in the *pseudocode*; the code that follows should be in whichever language the project uses, and this module shows it in four (The Same Algorithm in Four Languages).
  • "Framework-neutral means the framework does not matter." It matters enormously for where the operation is called from and how its rejection is shown; the point is that it does not change what the operation does, so it should not appear inside it (The Framework Comes Last).
  • "Write it once neutrally and the framework code writes itself." The wrapper is real work with its own decisions — optimistic updates, status codes, transactions — and the neutrality buys you that those decisions do not leak back into the cart.

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.

  • GENERALEvery operation on every concept has a contract that is independent of the language and framework it will run in; keeping the pseudocode to that contract is the same move for a cart, a login or a job queue.
  • DOMAIN-SPECIFICSome domains are framework-shaped by nature — a realtime chat's "broadcast to the room" or a UI's "re-render on change" is closer to the operation's core than a cart's 400 is — and there the boundary between contract and wrapper is drawn further out, with the side effect inside the contract.
  • ILLUSTRATIVEThe React, Express and Django flavours and the V-numbered versions are the concept record's invented progression, used to show the same operation under three wrappers; no real application is described.

Where the depth lives

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