DecompositionGENERALSTAGE-SPECIFICILLUSTRATIVE

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.

The question

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 situation

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.

The reflex

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.

Why it stalls

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.

What the reflex produces — and fails to produce
  • 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.
ProblemUnderstandRequirementsConstraintsUnknownsDecompositionSmallest StepModelExperimentObserveDebugLearnIterate

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.

The store, first two levels
E-commerce platform — V1
  • Catalogthe 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.
  • Cartholds 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.
  • Checkoutturns 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.
  • Orderswhat 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.
  • Paymentsthe 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.
  • Inventoryhow 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.
  • Adminsomeone 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.

Two decompositions of the same store
By layer
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.
By capability
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.

One order that follows from the tree
  1. 1
    Admin creates a product; Catalog lists it

    because Nothing depends on anything else here, and every later leaf needs products to exist.

  2. 2
    Cart add / remove / persist

    because Checkout reads the cart; the persistence decision has to be made before checkout can assume anything.

  3. 3
    Checkout 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.

  4. 4
    Payment 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.

  5. 5
    Orders list, then failures

    because The happy path works, so each failure can be injected against something that already runs (Failure Path Second).

a different valid order Risk-first: prototype the Payments leaf on the first day, with no catalog and no cart — a test-mode charge and a confirmation handler — and only then walk the tree from Catalog. Choose this when nobody on the team has integrated the provider and a surprise there (a mandatory hosted page, say) would reshape Checkout.

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.

Next questions
  • ?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

How the move itself fails
  • 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.
What the move costs
  • 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.
Misreads
  • "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.

Architecturemodular-monolith
Further
  • 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.