PrimitivesGENERALTEAM-SPECIFICILLUSTRATIVE

Go One Primitive Lower

The button that says "I still don't understand how to implement this" does one thing: it makes the thing smaller. addItem → Find Item → Loop Through Array → Compare IDs. At some rung you already know what to type, and that rung is the 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.

The question

You have the pseudocode for addItem in front of you and it still does not turn into code. What is the one move that always makes progress — and how far down does it go?

The situation

The concept builder shows item = find entry in cart.items with item.productId == productId. You read it and nod. You try to write it and your hands stop. "Find entry" is not a thing your editor knows. You are not sure whether you are stuck on the cart, on the language, or on yourself.

The reflex

Read the reference implementation. It is one click away, it is four languages, and cart.items.find((i) => i.productId === productId) is a single line; reading it feels like the fastest way to become unstuck. Or search "find item in array typescript" and paste the first answer, which is the same line.

Why it stalls

The line is copied and the stuck feeling is intact. find with an arrow function is exactly the part you did not understand, so you now have an addItem you can run and cannot modify — the next requirement ("find by product id and size") has no place to go.

What the reflex produces — and fails to produce
  • The line is copied and the stuck feeling is intact. find with an arrow function is exactly the part you did not understand, so you now have an addItem you can run and cannot modify — the next requirement ("find by product id and size") has no place to go.
  • The search result is for a different question. "Find item in array" gives you the syntax; your question was "what does finding mean here, and what would I do if the language had no find?" — the question whose answer would have transferred to removeItem.
  • Progress is measured by lines in the editor and the count went up by one. Ask what you could now write from a blank file and the answer is still nothing.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

The move

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

  • If you do not know how to build the thing, make the thing smaller until you reach something you do know how to build. That is the whole principle, and it is a button, not a course: press it once and the thing you are stuck on is replaced by a smaller thing. Press it again if the smaller thing is still too big.
  • Each press names the next rung. addItem is stuck → the stuck part is Find Item → Find Item is "look at each entry" → looking at each entry is Loop Through Array → inside the loop, the only work is Compare IDs. Four presses, and the last rung is an if with an == in it, which is something everyone who has written any code has written.
  • Notice what you are asking at each rung. Not "how do I write this?" but "what is this made of?" — the first question invites a search for syntax, the second produces the next rung. Syntax is looked up once, at the bottom, for a thing you already understand.
  • Then climb: the comparison goes inside the loop, the loop becomes findItem, findItem goes inside addItem. The line cart.items.find(…) is what the climb collapses into once you have written the loop by hand — and now it reads as a loop you understand rather than a spell.

The ladder the button walks

Each rung is one press of "I still don't understand how to implement this". Reading downward, every level says what it is made of; reading upward, every level says why the one below it exists. The ladder stops where this learner's knowledge starts — for someone else it would stop one rung higher, and the cart would come out the same.

The bottom rung is deliberately boring. A comparison inside a loop is the kind of thing nobody would put in a lesson, and that is the point: the principle works because the boring thing is already known.

addItem, one primitive lower each press
  1. addItem
    Check product, check quantity, find existing entry, increase or append, return the cart.The operation as the concept wrote it; the stuck part is one step of it.
  2. Find Item
    Given a product id, hand back the entry with that id, or nothing.The step that could not be written; named so it can be tested alone and used as one line above.
  3. Loop Through Array
    Visit each entry of cart.items, in order, once."Find" means "look at each until one matches"; the looking is a loop. If loops are new, this is where Arrays is one link away.
  4. Compare IDs
    item.productId == productId — true or false for this entry.The only work inside the loop. An equality on two strings is something already known, so the button stops here.

The bottom rung, then the rung above it

Pseudocode first, because the algorithm is language-neutral and the concept ladder shows the same thing in four languages. The bottom rung is the loop; the rung above uses it as a single call. When the reference collapses the loop into find, the behaviour is unchanged — which you can now check, because you wrote the long form.

findItem, then addItem using it
1function findItem(cart, productId):
2 for each item in cart.items: -- Loop Through Array
3 if item.productId == productId: -- Compare IDs
4 return item -- return early on the first match
5 return nothing
6
7function addItem(cart, productId, quantity = 1):
8 if not catalog.has(productId): reject "unknown product"
9 if quantity <= 0: reject "quantity must be positive"
10 existing = findItem(cart, productId) -- the rung below, as one line
11 if existing exists:
12 existing.quantity = existing.quantity + quantity
13 else:
14 append { productId, quantity } to cart.items
15 return cart

The two comments in findItem are the two bottom rungs. The reference's cart.items.find((i) => i.productId === productId) is this loop with the comparison passed in — same visits, same early return, O(n) either way.

Asking for the rung, not the syntax

The question you ask when stuck decides whether you get a rung or a spell. "How do I find an item in an array in TypeScript" returns syntax for a thing you do not yet understand. The best form asks what the step is made of, which is answerable without any language and transfers to the next operation.

The same stuck moment, asked three ways
vagueHow do I write addItem?
betterHow do I find an item in an array in TypeScript?
bestWhat does "find the entry with this product id" consist of, if the language had no find — and which of those pieces can I already write?

why The best form produces the ladder: a loop, a comparison, an early return. It can be answered on paper, it makes the reference's one-liner readable afterwards, and it is the same question that unlocks removeItem and total. The middle form produces a line that works and teaches nothing about the next line.

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.

  • Say out loud the exact line or phrase you cannot write. Not "addItem" — "find the entry". Precision about where you are stuck is most of the move (Unknown to Specific Question).
  • Ask "what is that made of?" and write the answer as a rung below it. One rung per press; do not skip to the bottom.
  • Stop at the first rung you could write with your eyes closed. If you cannot say whether you could, try to write it — thirty seconds decides it.
  • Write that rung as its own function with a name from the concept, test it with one example, then climb one rung and use it (Implement One Operation).
  • If a rung is not "too big" but "I do not know what this word means" — callback, map, iterator — that is a foundation, not a size problem; route it and come back (When the Rung Below Is a Foundation).

Worked on a concrete problem

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

  • Press one: "I cannot write addItem" → the checks and the branch are fine; the stuck part is "find the existing entry". Press two: "find the existing entry" → look at every entry until one has the right product id. Press three: "look at every entry" → a loop over cart.items. Press four: "the right product id" → item.productId == productId. Stop: this is an if.
  • Written from the bottom: for each item in cart.items: if item.productId == productId: return item and, after the loop, return nothing. Tested: on [Laptop × 1] with "laptop" it returns the entry; on [] it returns nothing. Named findItem.
  • Climbed: existing = findItem(cart, productId) then if existing: existing.quantity += quantity else: append { productId, quantity }. The example "Laptop × 1, add Laptop → Laptop × 2" runs through it by hand, with the loop visiting one entry and matching.
  • The reference's one-liner, read afterwards: cart.items.find((i) => i.productId === productId) is the loop with the comparison handed in as a function. It is shorter, it is the same thing, and the learner can now say which line would change if the rule became "find by product id and size".

How you know it worked

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

  • You can name the rung you are on. "I am stuck on Find Item, not on the cart" is a different sentence from "I cannot do this".
  • The bottom rung was written in under a minute, because it was already known — the button stopped at the right place.
  • The one-liner in the reference reads as a compression of something you wrote, not as a thing to memorise.
  • The next stuck moment is shorter, because "what is this made of?" is now the first thing you ask.

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 the exact phrase I cannot write — one step, not the whole operation?
  • ?What is that step made of, and can I write any of the pieces right now?
  • ?Is this rung too big, or is it a word I do not know — and which of those needs a different move?
  • ?Once the bottom rung works, which rung above it uses it, and what example proves that level?

What can go wrong

How the move itself fails
  • Pressing the button past the boundary. Going from "compare ids" to "how does string equality work in memory" is a fascinating detour that does not produce addItem; stop at the first rung you can write.
  • Pressing it on the wrong thing. If the whole cart is on the stuck line, the first press must pick one operation; "make the cart smaller" gives "make addItem" and nothing smaller than that until you choose.
  • Treating the button as a way to avoid learning. A learner who cannot write a loop at all will press it into "visit each element" and be stuck at a foundation; the button finds the gap, it does not fill it.
  • Never climbing back. The point of the bottom rung is to be used by the rung above it; a folder of tiny functions that were never assembled into an operation is the recursion without its return.
What the move costs
  • Writing the loop by hand and then reading the one-liner takes longer than reading the one-liner. The extra minutes buy the ability to write the next one-liner unaided.
  • The hand-written loop is more code than find; in production it would be replaced. The replacement is safe precisely because the loop was understood first.
  • The button is personal and a lab can only approximate where it stops for you; it may offer a rung you did not need, and you should skip it.
Misreads
  • "So the right way to write addItem is with a for loop." The reference uses find; the loop is how you earn the right to use find. Once you can say what find is made of, use it.
  • "Going lower means learning the language internals." One rung lower than the stuck line, not all the way to the machine. The button stops at the first thing you can write.
  • "If I have to go this low, I am not ready for the cart." The cart is a beginner concept precisely because its primitives are a loop and a comparison; reaching them is finishing the cart, not failing it.

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.

  • GENERALMaking a thing smaller until a known piece appears applies to any implementation, in any language, at any level of experience; what varies is the rung it stops on.
  • TEAM-SPECIFICA solo learner meets the button at "find item"; an engineer new to a language meets it at "how do I express a loop here"; an engineer new to a domain meets it at the operation. Each stops one rung below their own boundary — see the next lesson.
  • ILLUSTRATIVELaptop × 1, Laptop × 2, four presses and the thirty seconds are for the shape of the move; nothing was timed.

Where the depth lives

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