Decomposing a Problem
"Build an e-commerce platform" cannot be built; "list products", "add to cart" and "create an order" can. Decomposition is the move from the one to the other — and the split is judged by whether each piece could be built and checked on its own.
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.
A problem is too big to hold in your head at once. How do you split it so that every piece is something you could actually build and check, without the split itself being a week of diagrams?
The brief is "build the e-commerce platform" and you have accepted it. You have a page of requirements from the framing conversation and no idea which line to start on. Every line seems to depend on every other line: you cannot check out without a cart, cannot have a cart without products, cannot pay without an order.
Draw the architecture. Boxes for the web app, the API, the database, maybe a queue; arrows between them. It is the picture in every system-design article and it gives the whole thing a shape you can point at, which feels like the problem has been understood.
The diagram has three or four boxes, and each box is still the whole problem. "API" contains catalog, cart, checkout, orders, payments and admin, undifferentiated; nothing in the drawing says which of those to build first or how to tell when one is done.
- The diagram has three or four boxes, and each box is still the whole problem. "API" contains catalog, cart, checkout, orders, payments and admin, undifferentiated; nothing in the drawing says which of those to build first or how to tell when one is done.
- The boxes are the same boxes for every application ever built. A chat app, a URL shortener and the store all decompose into "frontend, backend, database" — so the drawing cannot have captured anything specific to the store.
- Work starts on whichever box looks most familiar, usually the database schema, and stalls when a schema decision needs a product decision ("does a cart survive logout?") that the diagram never asked.
- Progress is measured by boxes filled in rather than workflows completed, so after weeks the store has a beautiful data layer and no way for a customer to buy anything.
The move
Precisely enough to apply it to a problem you have never seen — not a slogan.
- Split along what the system *does*, not what it is made of. Ask "what can each actor do?" and let the answers become the children of the root: for the store, Catalog, Cart, Checkout, Orders, Payments, Inventory, Admin. Each child is a capability someone could recognise from the outside.
- For each child, ask whether you could build it alone and show it works. If not, it is not yet a subproblem — split it again, along the same question. Stop when every leaf has an observation attached: the thing you would look at to say "this piece works".
- Write down which pieces need which others to exist. Checkout needs a cart to read and an order to write; Catalog needs nothing but products. The dependency edges are what turn a tree into an order of work.
- Treat the tree as a working document, not a deliverable. It will change the first time a leaf turns out to be two problems, and that change is the decomposition doing its job — a tree that never changes was drawn after the work was done (Requirements Emerge During Implementation).
Split along behaviour, not along materials
The reflex splits the store into what it is built from. The move splits it into what it lets people do. The difference is not stylistic: the second split gives every piece a test, and the first gives none. Below is the store's first two levels, with each leaf carrying the observation that would show it works.
The children are not equal in size or risk, and that is fine. The tree's job is to make the problem addressable, not symmetrical.
- ├Catalog— the customer's first action
- └List productstestable The page shows every product the admin created, with name and price.
- └View one producttestable A real id shows name, price and stock; a missing id gives a clear not-found.
- ├Cart— holds intent between browsing and buying
- └Add, change quantity, removetestable After add twice, remove once, the cart holds one line with quantity one.
- └Cart persists across a refreshtestable Reloading the page shows the same lines — the persistence decision made explicit.
- ├Checkout— turns a cart into an order and a payment
- └Validate and totaltestable An out-of-stock line is rejected with a reason; the total equals the sum of current prices.
- └Create order and take paymenttestable One checkout yields exactly one order; a test-mode payment marks it paid, a failed one leaves it unpaid and says so.
- ├Orders— what was bought, and what happens to it after
- └Customer sees their orderstestable The order list shows the orders this customer placed and no one else's.
- ├Payments— the conversation with a provider outside the system
- └Confirmation from the providertestable The provider's confirmation reaches our system and marks the right order paid, once, even if delivered twice.
- ├Inventory— how many exist, and who may take one
- └Stock decreases on ordertestable After an order for a quantity, the product's stock is lower by that quantity and never below zero.
- ├Admin— someone has to create the products
- └Create and edit a producttestable A product created in admin appears in the catalog with the same price and stock.
Search, recommendations, coupons and returns are absent on purpose, and the reason for each absence is written next to the tree.
The same problem, split the other way
It is worth seeing the reflex's split beside the move's, because the reflex's split is not stupid — it is the split that every deployment eventually has. It is simply the wrong question to ask first.
Frontend (all pages), Backend (all endpoints), Database (all tables). Every child contains the whole product; nothing can be finished until everything is; no child has a test that a customer would recognise.
Catalog, Cart, Checkout, Orders, Payments, Inventory, Admin. Each child is a thing a customer or admin does, has a test in their words, and can be built to a working state while the others are stubs.
A capability cuts through all three layers, so finishing one proves the layers connect and delivers something usable; a layer cuts through all capabilities, so finishing one proves nothing about the product and delivers nothing.
From tree to first step
A tree with dependencies is already most of an implementation order. The sequence below follows from the edges — Catalog before Cart before Checkout — and from one judgment about risk: Payments is researched before it is on the critical path. It is one defensible order; the alternative is another.
- 1Admin creates a product; Catalog lists it
because Nothing depends on anything else here, and every later leaf needs products to exist.
- 2Cart add / remove / persist
because Checkout reads the cart; the persistence decision has to be made before checkout can assume anything.
- 3Checkout without payment: validate, total, create order
because Reaches "an order exists" before the riskiest integration, so the core workflow is testable end to end with a stubbed payment.
- 4Payment spike, then Payments leaf, then Inventory decrement
because The spike answers "who says it is paid?" before code depends on the answer; inventory follows because it fires on the same event.
- 5Orders list, then failures
because The happy path works, so each failure can be injected against something that already runs (Failure Path Second).
How to do it
Most important first.
- Start from the actors and their actions, which the framing step already produced (Problem Framing). Group the actions; each group is a candidate child of the root.
- Name children with nouns a customer or admin would recognise — Cart, Checkout — not with technical layers. If a child's name is Frontend or Database, you have split the solution, not the problem (Decomposition by Layer).
- For every leaf, write the observation that would show it works, in one sentence. If you cannot, split further or admit the leaf is a heading (What Makes a Good Subproblem).
- Mark the dependencies between siblings with arrows. If everything depends on everything, the split is wrong; try splitting along a different question (The Dependency Map).
- Stop when the next step is obvious: some leaf with no unmet dependencies and a clear test. Build that, then return to the tree (What to Build First).
Worked on a concrete problem
The move has to produce something. This is what it produced.
- The store, first split: Catalog (browse and view products), Cart (hold what the customer intends to buy), Checkout (turn a cart into an order and a payment), Orders (what the customer bought, and its lifecycle), Payments (the conversation with the provider), Inventory (how many of each thing exist), Admin (create products, change stock). Seven children; each is a word the founder would use.
- Second split of one child, Catalog: "list products" — the page shows every product the admin created; "view one product" — name, price, stock for a real id, a clear not-found for a missing one; "filter by name" — typing part of a name narrows the list. Three leaves, each with an observation, each buildable in an afternoon.
- Dependencies: Catalog and Admin need only products. Cart needs Catalog. Checkout needs Cart, Inventory and Payments. Orders needs Checkout. So the first buildable leaf is in Catalog or Admin, and the riskiest — Payments — sits behind Checkout, which is a reason to research it early even though it cannot be finished early (Risk-First Development).
- What was *not* in the tree: search ranking, recommendations, coupons, returns. They were on the founder's list. They are written beside the tree as "later" with a reason each, because a tree that contains everything is not a decomposition but a table of contents.
How you know it worked
What now exists that did not before, and what question you can now ask.
- Every leaf of the tree has a sentence beginning "you can tell it works when…", and none of the sentences mentions a framework.
- You can name the first thing to build and the reason is structural — nothing depends on it, or everything does, or it is the riskiest — rather than "it is what I know".
- A non-engineer can read the top level of the tree and recognise their product; an engineer can read a leaf and start.
- The tree changed during the first day of building, and the change was obvious to make.
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 can each actor do, and does each of those become a child of the tree?
- ?For this leaf, what would I look at to say it works?
- ?Which pieces need which other pieces to exist before they can be built?
- ?Which leaf has no unmet dependencies and a clear test — and is therefore the next thing to build?
- ?What did I leave out of the tree on purpose, and did I write down why?
What can go wrong
- Decomposition for its own sake: the tree reaches four levels and forty leaves before anything is built, and the leaves encode guesses about parts of the system nobody has touched. Decompose the first branch to leaves and the rest to one level; recurse when you arrive (Recursive Decomposition).
- Splitting along the solution: the children are Frontend, Backend, Database, and every product question is still unanswered. The split looks complete because every piece of code has a home; it is not, because no piece of behaviour does.
- Leaves without observations. "Handle payments" is a leaf in name only; nobody can say when it is done, so it is done when time runs out.
- A tree drawn once and never revised, so that when checkout turns out to need inventory reservation the new piece has nowhere to go and gets bolted onto whichever leaf is nearest.
- A decomposition costs the time it takes to write and the argument it starts about scope; a diagram of boxes costs neither, which is exactly why it is the reflex.
- A tree fixes a view of the problem, and every later discovery has to be reconciled with it. That is friction — useful friction, because it forces the discovery to be named, but friction.
- Capability-first splits can cut across technical concerns that are genuinely shared — authentication, logging — and those need a home the tree does not naturally give them.
- "So the architecture diagram is wrong." It is premature, not wrong. The same boxes are drawn later, when the capabilities say which boxes are needed and what each must do; then the diagram is *about* something (Architecture From Requirements).
- "Decompose the whole thing before building." No: decompose until the next step is obvious, build it, and let what you learned reshape the tree. The full tree is what you have at the end, not the beginning.
- "Seven children means seven services." A decomposition is a map of the problem, not a deployment plan. All seven can live in one process for a long time; splitting deployment is a separate decision with its own evidence (Modular Monolith in Architecture).
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 along what the system does, then recursing until each leaf has an observation, works for any problem with observable behaviour — a store, a compiler pass, a data pipeline. For a pure library the "actors" are callers and the capabilities are the operations they invoke.
- STAGE-SPECIFICOn a greenfield store the tree is invented from the requirements; in an existing store the tree already exists in the code, however badly, and decomposing means finding it — the capability a change touches and the leaves it needs — rather than drawing a fresh one.
- ILLUSTRATIVEThe store, its seven children and the afternoon-sized leaves are invented to show the shape of the move; a real store would split differently depending on what it sells.
Where the depth lives
This domain asks the question and hands the answer off by name.
- — The manifesto's layers page at /manifesto/layers is about the same distinction from the other side: the layers exist, but understanding travels through behaviour, not through them.