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 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.
Your pseudocode has a useState in it. What went wrong, and what is the pseudocode supposed to be neutral *from*?
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.
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.
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 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.
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
===, noNone, nostd::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?
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 => …).
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.
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.
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.
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 untouchedA 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.
- OperationThe eight neutral lines: catalog check, quantity check, find, branch, mutate or append, return.
- TestCalls addItem twice on an in-memory cart and asserts [Laptop × 2] — no framework loaded.
- Route (Express)POST /cart/items: parses the body, calls addItem, returns the cart or 400 with the rejection message.
- Hook (React)Holds the cart in state, calls addItem on click, replaces the state with the result or shows the rejection.
The implementation ladder
Concept, examples, pseudocode, code, tests, production — for the concept this lesson is about. Code is the fourth tab, not the first.
Shopping Cart = A temporary collection of products the user intends to purchase, held between browsing and checkout.
- 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.
- 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.
- 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
- • 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 doestry 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.itemsin memory. A repository will later load the cart, calladdItem, 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.
- ?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
- 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.itemsbecomescollection,productIdbecomeskey, 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.
- 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
setCartfaster thanappend … 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.
- "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.