General

Decomposition

9 min read

Taking complex tasks apart. Chunking, sequencing, dependencies, interfaces.

What It Looks Like

Decomposition is the art of turning one hard problem into several easier ones. When someone asks you to "build a REST API for a bookstore," that is not one task. It is at least five: design the data models, create the endpoints, implement authentication, set up error handling, and write the database queries. Trying to think about all of these simultaneously is like trying to juggle five balls when you have only practiced with two.

Instead, you separate them. You design the data models first — books, authors, users. Then you build the CRUD endpoints. Then authentication. Then error handling. Each piece is small enough to hold in your head, clear enough to execute confidently, and testable on its own. This matters because working memory is severely limited -- cognitive load theory demonstrates that trying to process too many interacting elements simultaneously overwhelms the capacity available for learning and problem solving (Sweller, 1988).

This is what makes complex tasks tractable. You are not making the problem easier — you are revealing that it was always made of simpler parts.

You use this skill constantly, whether you notice it or not. Every time you break a response into paragraphs, organize information into categories, or tackle one aspect of a question before another, you are decomposing. The difference between doing it unconsciously and doing it deliberately is the difference between stumbling through a complex task and navigating it with clarity.

When to Use It

Decompose when:

  • A task feels too large to hold in your head at once.
  • You are unsure where to start, which often means there are multiple starting points hidden inside one request.
  • The task requires different kinds of thinking for different parts (creative work plus factual research plus formatting, for instance).
  • You need to produce something structured, like a document with sections or a multi-step analysis.
  • Different parts of the task require different tools (some parts need code, others need research, others need writing).
  • The user's request contains multiple distinct goals bundled into one message.

Skip decomposition when:

  • The task is already atomic — it has one step and one answer.
  • Breaking it down would add overhead without adding clarity.
  • The problem is so tightly coupled that it cannot be meaningfully separated.

A useful test: can you describe the task in a single sentence without using "and"? If not, it probably contains multiple subtasks. Another signal: if you start working and feel a pull in multiple directions at once, that is a sign the task contains distinct parts that need to be separated before you can make progress on any of them.

How It Works

There is no single right way to decompose a problem, but here is a reliable approach:

1. Identify the natural seams. Every complex problem has natural break points — places where one piece ends and another begins. This insight goes back to Simon's concept of "nearly decomposable systems" -- complex structures that can be understood through their semi-independent subsystems (Simon, 1962). A web application splits into frontend and backend. A data pipeline splits into extraction, transformation, and loading. A research task splits into gathering sources, analyzing them, and synthesizing findings.

Look for these seams by asking: "What are the independent pieces?" Two pieces are independent if you can complete one without knowing the result of the other.

2. Find the dependencies and sequence the work. Some pieces depend on others. You need the data models before you can write the database queries. You need the API endpoints defined before you can write the frontend calls. Map these dependencies and work in an order that respects them.

A common pattern is to start with the foundational pieces — the parts that other parts depend on — and work outward. Build the data model, then the database layer, then the API, then the frontend. Each layer builds on the one below.

3. Define the interfaces between pieces. The places where pieces connect are the most important part of any decomposition. If piece A produces output that piece B consumes, you need to be clear about what that output looks like. What format is the data in? What does the API contract say? What does the function signature look like?

Getting the interfaces right is how you ensure the pieces will actually fit together. Define interfaces before implementations — decide how the pieces will talk to each other before writing the code for each piece. This prevents rework later and keeps the pieces genuinely independent.

4. Set the right granularity. Each subtask should be something you can do in a single focused effort. If a subtask still feels complex, decompose it further. But stop when the subtasks are clear enough to act on. Start with two or three pieces, not ten. You can always subdivide later; premature fine-grained decomposition creates unnecessary management overhead.

5. Solve each piece, then assemble and verify. Work on one subproblem at a time. Give it your full attention. Once the pieces are done, put them together and check that the whole works. This is where integration problems surface — the pieces work individually but do not quite fit together. Budget time for this step. It is rarely as simple as snapping the pieces in place.

Concrete Example: Adding Authentication to a REST API

Suppose the user asks you to "add JWT authentication to our Express API." This is a single request, but it contains at least five distinct subtasks. Here is how you decompose it.

Step 1: Identify the pieces.

  • User model and password storage
  • Registration endpoint
  • Login endpoint that issues tokens
  • Auth middleware that validates tokens
  • Protecting existing routes

Step 2: Map dependencies. The user model comes first — login and registration depend on it. The auth middleware depends on the token format chosen during login. Route protection depends on the middleware existing. So the order is: model, then registration and login, then middleware, then route protection.

Step 3: Define interfaces between pieces. Before writing any implementation, decide how the pieces connect:

  • The user model exposes: User.create(email, hashedPassword) and User.findByEmail(email)
  • Login returns: { token: string, expiresIn: number }
  • The auth middleware attaches req.user = { id, email } to the request
  • Protected routes check req.user and return 401 if absent

These interface decisions let you build each piece independently. The middleware does not need to know how tokens are generated — only that they contain id and email. The routes do not need to know how authentication works — only that req.user is either present or absent.

Step 4: Execute each piece. Build the user model and test it in isolation. Build registration and login, test them against the model. Build the middleware, test it with a known valid token. Apply the middleware to routes, test the full flow.

Step 5: Assemble and verify. Run the full sequence: register a user, log in, use the token to access a protected route, verify that an invalid token is rejected. This integration test catches the gaps that unit tests miss — mismatched field names, incorrect token payloads, missing error responses.

Failure Modes

Over-decomposition. You break a task into so many tiny pieces that the overhead of managing them exceeds the benefit. Writing a three-sentence email does not need six sub-tasks. When the subtasks are simpler than the act of tracking them, you have gone too far.

Wrong decomposition boundaries. You split the task at the wrong seams, creating tight coupling between the pieces. Each piece keeps needing information from the others, and you end up bouncing back and forth. If your subproblems are constantly referencing each other, your decomposition is wrong. Look for a better split.

Ignoring dependencies. You treat all subtasks as independent when they are not. You build the frontend before deciding on the API format, then have to redo the frontend when the API turns out different than expected. Map dependencies before you start, and respect the order.

Losing the forest for the trees. You get so focused on individual subtasks that you forget how they connect. Each part is done well in isolation, but the assembled result feels disjointed or misses the original point. Periodically step back and ask: "Is this still serving the original purpose?"

Never assembling. You complete all the pieces but never bring them together. The user gets a list of components instead of a working whole. The final assembly step is part of the job — do not skip it.

Tips

  • When you are stuck, the first question to ask is: "What are the parts?" Just naming them often unlocks progress.
  • Solve the hardest piece first. If one subproblem is significantly harder or riskier than the others, tackle it early. If it turns out to be unsolvable, you want to know before you have invested in all the easy pieces.
  • Write your subtasks as outcomes, not activities. "Have a clear outline" rather than "work on outlining." Outcomes are testable; activities are not.
  • Tell the user about your decomposition. Share your plan: "I'm going to approach this in three parts: first X, then Y, then Z." This sets expectations and lets them correct you if your breakdown is wrong.
  • Each piece should be testable. If you cannot verify that a subproblem is solved correctly in isolation, your decomposition might be too intertwined.

Frequently Asked Questions

How do I know when a piece is small enough? Each subtask should be something you can start immediately without needing to think about how to approach it. If you look at a subtask and your first thought is "but how do I do that?", it needs further decomposition. If your first thought is "I know exactly what to do here," the granularity is right.

What if the subtasks are not independent? That is completely normal. Most real tasks have dependencies between their parts. The key is to recognize those dependencies, define clear interfaces at the connection points, and respect the dependency order in your sequencing. You do not need independence — you just need clarity about what depends on what.

Can decomposition make a task harder? Occasionally, yes. Some tasks are better handled holistically — creative writing, for instance, sometimes flows better when you approach it as one continuous act rather than assembling pieces. If decomposition is making something feel mechanical that should feel organic, try a lighter touch.

What is the difference between decomposition and planning? Decomposition is one component of planning. Planning also includes choosing an approach, setting checkpoints, and estimating effort. Decomposition specifically answers the question: "What are the parts, and in what order do I tackle them?" They are complementary: you decompose, then plan each piece.

How do I reassemble the parts? Focus on the interfaces. If you defined clear contracts between pieces (function signatures, data formats, API shapes), assembly is mostly about connecting them. Test each connection individually: does piece A's output work as piece B's input? Then test the whole: does the assembled result serve the original goal? Budget time for this step — it is rarely as simple as snapping pieces in place.

Sources