Make It Smaller Until You Know How to Build It
If you do not know how to build the thing, make the thing smaller until you reach something you do know how to build. Cart → addItem → find the existing item → loop through the collection → compare two ids. The last one you can write; build back up from there.
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 are stuck on a thing you cannot build and every attempt starts from the top. How do you find the size at which you *can* build it, and how do you get back up to the thing you needed?
You have the cart's state, operations and rules on a page. You start addItem and stop at "look for an existing entry with this product id". You know what that sentence means and you cannot make it into code. It is one line of the plan, and it is the line that blocks the whole cart.
Search for the whole thing again — "typescript find item in array by property" — and paste the answer. It works, the cart adds, and you do not know why find takes a function or what it returns when nothing matches, which is exactly the case the edge example needs.
The paste works for the happy case and returns undefined for the missing case, and because you did not write the loop you do not know that undefined is the "not found" signal, so the else branch is written wrong.
- The paste works for the happy case and returns undefined for the missing case, and because you did not write the loop you do not know that undefined is the "not found" signal, so the else branch is written wrong.
- The next line — "increase its quantity" — needs to mutate the found entry, and whether
findreturns a copy or the entry itself is now a mystery, so you guess, and the quantity does not change. - Every subsequent stuck is handled the same way, so the cart is assembled from five pastes with no primitive understood, and the first modification — "cap the quantity" — has nowhere to go.
- The size of the stuck is never measured. "I can't build a cart" and "I can't compare two strings" feel the same from inside, and one of them is a minute away.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- State the principle exactly, because its exactness is what makes it usable: *If you do not know how to build the thing, make the thing smaller until you reach something you do know how to build.* The move is not "learn more"; it is "descend until the unknown is a primitive you already have", then climb back up one level at a time.
- Descend by asking "what does this need?" of the thing you cannot do. The cart needs addItem, removeItem and total. addItem needs find-existing, append and reject. find-existing needs a loop over the collection, a comparison of two ids, and returning early from inside the loop. At "compare two ids" you stop, because
a == bis something you know. - Build the level you can, then the one above it with that as a part. Compare ids → a loop that compares each entry's id and returns the entry when they match, or nothing when the loop ends → find-existing → addItem, with the append and the reject as their own short descents → the cart. Each level is a function whose body is the level below plus one idea.
- If a level still will not go, the descent has not reached a primitive; keep going. If the descent reaches something that is genuinely not known — what a loop is — the missing thing has a name now, and the name routes to exactly one lesson, after which you return to the cart. That return is the difference between this move and tutorial hell.
The descent
The cart record's primitive graph, cut to the chain that matters for the stuck in this lesson. Every leaf is something the learner is assumed to know; the test on each leaf is that you could type it alone.
- ├addItem— the operation with the most rules
- ├Find the existing item— the rule "one entry per product"
- └Loop through the collectiontestable A loop over [ Laptop × 1, Mouse × 1 ] visits both entries and stops.
- └Compare two idstestable "laptop" == "laptop" is true; "laptop" == "mouse" is false.
- └Return from inside the looptestable The function returns the matching entry the moment it is seen, and nothing when the loop ends.
- └Append a new entrytestable After appending to [], the array has one entry with the given productId and quantity.
- └Reject an invalid inputtestable Quantity 0 produces an error and the collection is unchanged.
The chain stops at compare, loop, return-early, append and conditional because those are the learner's primitives. A different learner's chain stops higher or lower; the boundary is personal.
Climbing back up
Each level is the level below plus one idea. The pseudocode shows the climb from the comparison to addItem; the language is deliberately neutral, and the four-language version lives in the concept ladder.
1-- level 5: compare two ids (known)2item.productId == productId3 4-- level 4: loop + compare + return early = find existing5function findExisting(items, productId):6 for each item in items:7 if item.productId == productId: return item8 return nothing9 10-- level 2: find + append + reject = addItem11function addItem(cart, productId, quantity = 1):12 if not catalog.has(productId): reject "unknown product"13 if quantity <= 0: reject "quantity must be positive"14 item = findExisting(cart.items, productId)15 if item exists: item.quantity = item.quantity + quantity16 else: append { productId, quantity } to cart.items17 return cartNothing at level 2 is new; it is level 4 plus an append and two rejections. If any line here feels un-typeable, that line has its own descent.
The bottom level, traced
A trace of findExisting on the cart's add-again example shows what "return early" and "return nothing" mean in the two cases the else branch depends on.
- inputitems = [ { productId: "laptop", quantity: 1 } ], productId = "laptop"
- lookupLoop visits the first entry; compares "laptop" == "laptop".
- branchThe comparison is true, so return early with this entry. (On [] the loop never runs and the function returns nothing — the case that becomes the append.)
- mutationNone — find reads; addItem will mutate the returned entry's quantity.
- outputThe entry { productId: "laptop", quantity: 1 } — the same object, so the increase in addItem changes the 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.
- When stuck, write the thing you cannot build at the top of a page and, beneath it, what it needs; repeat on each need until a line is something you could type now (Big Unknown, Smaller Unknown, Known Primitive).
- Name the primitives you reached — loop, compare, return early, append, conditional — and check that each is genuinely known by writing it alone (Where the Primitives Start Is Yours).
- Build upward one function per level, testing each with a two-line example before the next (Implement One Operation).
- When a primitive is missing, take the one lesson that teaches it and come back — do not take the course (When the Rung Below Is a Foundation, Teach Me Only What I Need).
- Keep the chain written down; the next time addItem needs a change, the chain says which level it touches.
Worked on a concrete problem
The move has to produce something. This is what it produced.
- The chain, as the cart record has it: Shopping cart → addItem → find existing item → loop through collection → compare product ids. Five levels; the bottom one is
item.productId == productId, which is a comparison, and a comparison is known. - Climbing:
for each item in cart.items: if item.productId == productId: return item; return nothing— that is find-existing, and it is the loop plus the comparison plus returning early. It is tested with [ Laptop × 1 ] and "laptop" (returns the entry) and with [] and "laptop" (returns nothing). Now the else branch in addItem has a meaning: nothing came back. - addItem is find-existing plus one idea:
if item exists: item.quantity += quantity else append { productId, quantity }, with the two rejections above it. Append is the array primitive; reject is a conditional. The cart is addItem plus removeItem (a loop that keeps every entry except one — the same loop and comparison, different action) plus total (a loop with arithmetic). - A missing primitive, routed: a learner who reaches "loop through the collection" and has never written one does not start a JavaScript course. They take the array lesson, write one loop that prints each entry, and return to find-existing the same afternoon. The cart was the reason to learn the loop, and the loop was the whole detour.
How you know it worked
What now exists that did not before, and what question you can now ask.
- The stuck has a size: you can say which level of the chain you are on and what it needs.
- The bottom of the chain is something you wrote from memory in under a minute.
- Each level up is a function whose body you can explain as "the level below, plus this".
- When something was genuinely missing, it had a name, one lesson, and you came back.
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 does the thing I cannot build need — and which of those needs can I not build either?
- ?At which level does the descent reach something I could type right now?
- ?Building back up, what is the one idea each level adds to the level below?
- ?If a level is genuinely unknown, what is its name, and which single lesson teaches it?
What can go wrong
- The descent never stops. "Compare two ids" is descended into string encoding and equality semantics; the primitive boundary is where *you* stop knowing, not where the machine does.
- The chain is written and not climbed. Five levels on a page is a plan; the cart exists only when each level has been built with the one below it inside.
- The move is used on a thing that was already known. Descending "increase the quantity" into arithmetic is theatre; descend only what you cannot type.
- The return is skipped. The missing loop is learned, then the loop lesson's exercises, then iterators — and the cart is still empty. The route to a primitive is a round trip.
- The descent takes longer than a paste that works; the first time, five levels for one operation feels absurd.
- Small functions at every level is more code than one clever line; you may inline them later, once you know what they were.
- The move finds gaps you would rather not have found, and a chain that bottoms out at "what is a loop" is honest in a way that is not comfortable.
- "Make it smaller means reduce the scope of the feature." That is MVP thinking, and it is useful, but it is a different move. This one keeps the feature and shrinks the *step* until it is buildable; the cart is not made smaller, addItem is.
- "This is just decomposition again." Decomposition splits a problem into subproblems; this descends a single unknown until it hits a known primitive, and the test of success is "I can type this", not "this is a good subproblem".
- "Experienced engineers don't do this." They do it in their heads, fast, and without noticing; the visible chain is the beginner's version of a reflex that never goes away.
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.
- GENERALDescending an unknown to a known primitive and climbing back works in any language and for any concept — the chain for a rate limiter bottoms out at "compare two timestamps".
- TEAM-SPECIFICA solo learner writes the chain; a senior on a team runs it silently and the visible version is the question they ask a junior — "what does that line need?"
- ILLUSTRATIVEThe five-level chain and the Laptop examples are the cart record's own; the learner who has never written a loop is invented for the shape of the routing.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The manifesto's "Don't delegate understanding" at /manifesto: the paste answers the search; the descent answers the question. The route down and back is at /thinking/primitives.