Big Unknown, Smaller Unknown, Known Primitive
"Implement a cart" is too big to start. "Loop through an array" is not. The move between them is recursive: split the unknown into smaller unknowns until one of them is something you already know how to write, and that is where you start.
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.
You know what a cart is, what it remembers and what can happen to it — and you still cannot write the first line. How do you get from "implement the cart" to a line of code you actually know how to write?
The concept is done. You have the state — items, each with a product id and a quantity — the six operations, the rules, the examples. You open the editor to write addItem and nothing comes. You know what it should do; you do not know what to type. It feels like the understanding was fake.
Ask the AI for the cart. The concept work is finished, so it feels earned: paste the operations list into a prompt and get five functions back in the language of your choice. Or open a tutorial called "shopping cart in TypeScript" and type along. Either way the editor stops being empty, which is what was hurting.
The functions exist and you cannot say why line four is there. The find before the push is the rule "one entry per product", but it arrived as syntax, so when the next requirement (a maximum quantity) lands there is no line of your own reasoning to attach it to.
- The functions exist and you cannot say why line four is there. The
findbefore thepushis the rule "one entry per product", but it arrived as syntax, so when the next requirement (a maximum quantity) lands there is no line of your own reasoning to attach it to. - The tutorial's cart is not your cart. Its items carry a name and a price because that made the demo render; you had already decided those are derived. You now own a data structure you argued against.
- The block returns at the next operation. Nothing about receiving
addItemtaught you how to writechangeQuantity, so the reflex repeats, and the number of functions you can write from a blank editor is still zero.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Treat "I cannot write this" as a size problem, not an ability problem. The thing you are staring at is a Big Unknown; the move is to name the Smaller Unknowns it is made of and ask of each one: can I write this? If not, split it again. The recursion stops the moment a piece is something you have written before — a Known Primitive — and that piece is where the code starts.
- Split along the operation, then along what the operation does. "Implement the cart" → "implement addItem" (one operation) → "check the product exists, check the quantity, find the existing entry, increase it or append" (its plain-English steps, which the concept already lists) → "find the existing entry" (the one step you cannot yet write) → "look at each entry and compare its product id to the one I was given" → a loop and a comparison.
- Stop splitting the moment a piece is familiar. The loop is a primitive if you have written a loop; if you have not, it splits once more into "visit each element" and "do something with it", and Arrays are where you go. The boundary is yours, which is why nobody can hand you the ladder pre-cut (Where the Primitives Start Is Yours).
- Build upward from the primitive. The loop-and-compare becomes
find existing,find existingplus a conditional becomesaddItem,addItemplus the other operations becomes the cart. Every level you climb is a piece you can now explain, because you built it from a piece you could already explain.
The tree, from the operation to the loop
The decomposition below is not the store's decomposition — that one split by capability and produced catalog, cart and checkout. This one starts at a single operation and splits by what the operation does, following the plain-English algorithm the concept already wrote. It is a different tree with the same rule: a leaf is something you could test on its own.
Only one branch goes deep. The other steps of addItem were already known and stop immediately; the tree records that they were checked, not that they were hard. The depth of the one branch is what "I cannot write this" was made of.
- └Check the product existstestable addItem with "keyboard" succeeds; with "toaster" it is rejected with "unknown product" and the cart is unchanged.
- └Check the quantity is positivetestable addItem with quantity 0 is rejected with "quantity must be positive"; the cart is unchanged.
- ├Find the existing entry for this product— the one step that could not be written
- └Visit each entry in cart.items in ordertestable A loop over [Laptop, Mouse] visits Laptop then Mouse and nothing else.
- └Compare the entry's productId to the one giventestable "laptop" == "laptop" is true; "laptop" == "mouse" is false — an equality on two strings.
- └Return the entry on the first match; return nothing if the loop endstestable findItem([Laptop × 1], "laptop") returns the Laptop entry; findItem([], "laptop") returns nothing.
- └Increase the quantity, or append a new entrytestable [Laptop × 1] + add Laptop → [Laptop × 2]; [] + add Laptop → [Laptop × 1] — the concept's first two examples.
The leaves under "find" are the primitive chain the concept records: loop through the collection, compare product ids, return early. Everything above them is assembly.
Building back up
The ladder reads bottom to top in the order the code is written. Each level says why it exists in terms of the level below it — which is the test of whether the climb was understood rather than pasted. If a level cannot say what it is made of, the recursion was skipped there.
Notice that the bottom rung is not "arrays" in general. It is the one array operation this problem needed. Going lower than the problem asks is the failure mode this module spends a whole lesson on (Teach Me Only What I Need).
- Loop and comparefor each item in cart.items: if item.productId == productId: return item — This is the piece that was already known. It is the base case of the recursion — the point where "I cannot write this" stopped being true.
- findItemThe loop wrapped in a function that returns the entry or nothing. — The step "look for an existing entry" needed a name so that addItem could use it as one line and so that it could be tested alone: findItem([], "laptop") → nothing.
- addItemThe two checks, then findItem, then increase or append, then return the cart. — The operation is its plain-English steps in order; the only step that was hard is now a call.
- removeItem, changeQuantity, totalThe same loop with a different body — filter, set, sum. — The primitives were reused, not regrown; the second operation is faster than the first because the tree already exists.
- The cartSix operations over one data structure, run against the concept's six examples. — The Big Unknown, assembled from pieces each of which was explained by the piece below it.
The move as a loop
Written as a procedure, the move has a base case and a recursive case, and a step most people skip: assembling upward with a test at each level. The slogan "break the problem down" is only half of it, and it is falsifiable in this form — a breakdown that stops on a piece you cannot write, or that never climbs back, has not solved anything.
- 1Name the unknown
Write the thing you cannot write as one line: "implement addItem".
fails by Naming the whole cart instead of one operation, so the first split is still too big to judge.
- 2Can I write it?
If yes, write it and stop. If no, continue.
fails by Answering "no" out of caution for a piece you could write — the ladder grows rungs the problem does not need.
- 3What is it made of?
List the two-to-five smaller pieces, along the behaviour, not the syntax.
fails by Splitting into "class, constructor, method" — pieces every function has, that say nothing about the cart.
- 4Recurse on the unknown pieces
Apply the same question to each piece you cannot write.
fails by Recursing on a piece you do not understand rather than cannot write; that is a foundation gap and needs routing, not splitting.
- 5Assemble and test upward
Write the primitive, wrap it, use it in the level above, run an example at each level.
fails by Writing the loop and calling the cart done.
The base case is personal: "something I have written before". Two people running this procedure on the same operation get trees of different depth and the same cart.
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.
- Write the Big Unknown as one line at the top of the page — "implement addItem" — and under it the plain-English steps from the concept (Plain-English Logic Is the Algorithm). Mark each step: can write / cannot write.
- Take the first "cannot write" and ask what it is made of. Write the answer as two or three smaller lines, indented, and mark those too. Keep going only on the "cannot write" lines.
- When you hit a line you know how to write, write it — the loop, the comparison, the return — as a function of its own with a name from the concept (Decomposing a Problem shows the same move on the whole store).
- Assemble upward: use the small function inside the step above it, and that step inside the operation. Run the concept's examples at each level, not just at the top (Examples Become Tests).
- If a leaf is not "cannot write" but "do not understand" — a
findwith a callback, a map — stop building and route the gap (When the Rung Below Is a Foundation), then return to the same line.
Worked on a concrete problem
The move has to produce something. This is what it produced.
- Cart → addItem → the six plain-English steps. "Check the product exists" is a lookup in a set: can write. "Check the quantity is positive" is an
if: can write. "Look for an existing entry with this product id": cannot write. "Increase its quantity" and "append a new entry": can write once the entry is in hand. One step is the whole block. - "Look for an existing entry" → "go through the entries one at a time" and "for each one, is its productId the one I was given?" and "if yes, stop and hand it back; if none matched, say so". A
forloop, an==, an earlyreturn. All three are things anyone who has written a loop has written. - Built upward:
findItem(cart, productId)is the loop with the comparison and the early return.addItemcalls it, then branches on the answer: increase or append. The concept's pseudocode for addItem is now readable as a sentence in a language you speak, and the example "Laptop × 1, add Laptop → Laptop × 2" runs through it by hand. - The same walk for
removeItemis shorter: "keep every entry whose productId is not this one" is the same loop with the comparison inverted and a new array collecting the survivors. Nothing new was needed; the primitives from addItem covered it.
How you know it worked
What now exists that did not before, and what question you can now ask.
- The page has a tree on it whose leaves are all things you have written before, and the tree's root is the operation you could not start.
- You wrote the bottom function first, and it is small enough to test with one example before anything uses it.
- When someone asks "why is there a find before the push?", the answer is the rule, and you can point at the leaf where the rule became a loop.
- The next operation was faster, because the leaves it needed were already on the page.
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.
- ?Which single step of this operation is the one I cannot write — and what is that step made of?
- ?What is the smallest piece here that I have written before, in any language?
- ?Can I test the bottom piece on its own, with one of the concept's examples, before anything uses it?
- ?When I climb back up, does each level explain the one below it — or did I paste something in the middle?
What can go wrong
- Splitting a piece you could already write. "Check the quantity is positive" does not need to become "compare two integers" and "what is an integer"; recursing below your own boundary produces a ladder with more rungs than the problem and no code at the end.
- Splitting along the wrong axis. "addItem needs a class, a constructor and a method" is a technical decomposition — every function needs those — and none of its leaves says what the cart does. Split along the behaviour: the steps in the plain-English algorithm.
- Reaching a primitive and not building back up. A learner who writes the loop and stops has a loop, not a cart. The recursion is only half the move; assembly is the other half, and it is where the operation is actually tested.
- Confusing "cannot write" with "do not understand". The first is a size problem and splitting fixes it; the second is a missing foundation and splitting only produces smaller things you also do not understand.
- The walk down produces a tree and several tiny functions before it produces the operation. For a cart that is minutes; for someone who could have typed
addItemfrom memory it is wasted minutes. - Small named functions at every level are excellent for learning and slightly noisy in production code; some of them will be inlined once the whole operation is understood.
- Recursing on every operation is slower than recursing once and noticing the leaves repeat — the second and third operations should reuse the tree, not regrow it.
- "So I should always write helper functions for everything." No — the helpers are scaffolding for a piece you could not write in one go.
removeItemin the reference is a single filter because, once the loop-and-compare was understood, it fit in one line. - "Recursive problem solving is a DSA topic." The name is borrowed and the idea is the same — a problem defined in terms of smaller instances of itself — but the base case here is "something I already know how to write", not "an array of length one".
- "If the tree gets deep, the problem is hard." Depth measures the distance between the problem and your current primitives, not the difficulty of the problem. The same cart is a one-level tree for someone who has written a hundred loops.
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.
- GENERALSplitting an unknown until a known piece appears works on any implementation task; what changes between learners and problems is where the split stops, not whether it applies.
- TEAM-SPECIFICFor a solo learner the primitives are loops and conditionals; for an experienced engineer meeting an unfamiliar domain the primitives are whole operations, and the tree is two levels deep. The move is identical; the depth is personal.
- ILLUSTRATIVEThe cart, Laptop × 2 and the six steps are the running example; the quantities and the number of levels are for the shape of the argument, not a measurement.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The manifesto's layers at /manifesto/layers describe the same descent from application to primitive across the whole stack; this lesson runs it inside one function.