Thinking in Pieces: Part II — Decomposition
When seeing clearly becomes the next step

Photo by Alexander Andrews on Unsplash
In the previous piece, we explored how machines reason differently from us, how they execute while we interpret. Decomposition is where that difference becomes a method we can learn from. It’s not about code; it’s about clarity.
Just to refresh our minds: decomposing is the act of breaking down a problem until each fragment feels almost trivial. Break it into bite-sized pieces. The goal isn’t to simplify reality, but to make it understandable enough to act on.
There are several ways to decompose a problem: from top-down to bottom-up, from functional to data-flow or by responsibility. The point here isn’t to discuss specific methods, but to give you an overall sense of how decomposition works and how you might apply it.
At this stage, it’s important to know that you can break a problem by function, data, actors, time, or even purpose. Just don’t get stuck in the taxonomy, only you can recognize the size of your own bite.
Let’s see how that looks when you actually try to think this way.
The Problem
Unless you’re playing a video game, problems in real life rarely arrive as puzzles waiting to be solved. They usually come as desires from people with perspectives other than the engineering team. In software development, for instance, they often originate from product or design half-formed ideas conceived during a meeting or inspired by client feedback. They’re usually simple enough to fit in one sentence. And honestly, I like when they come without any technical definition, because that’s the perfect scenario to apply our ability to decompose.
Sometimes, before we can decompose the problem itself, we have to look through it, uncover the dependencies, the invisible relationships, the way it will interact with other parts of the system, and so on.
But let’s start with a problem:
“The product team wants to improve engagement by making the system feel more personal. They propose that users should start seeing recommendations based on what they’ve already interacted with — likes on articles they’ve read, previously purchased items, or features they’ve explored. It doesn’t need to be perfect at first, just ‘smart enough’ to feel tailored.”
So, the first thing we need to do is identify the goal, in this case, it’s quite explicit: “users should start seeing recommendations based on what they’ve already interacted with.” Let’s call that simply “better recommendations.”
To reach it, we need to understand three basic principles of software development: Input, Processing, and Output.
The input is all the information we need before the system starts “thinking.” It doesn’t matter if it comes from direct user input, a database query, or data collection, it’s the raw material we work with. In this scenario, our input is the user’s past interactions with posts they’ve read or liked.
The output, by contrast, is what we deliver. It could be shown directly to the user, passed to another function, or saved to a database. The main idea is simple: we take input, we do something with it, and we produce a result. In our case, that result will be a list of recommended posts.
To connect these two pieces, we have the processing: the calculation, validation, and data manipulation needed to transform input into output. Here, we’ll need to analyze the posts a user has interacted with and find similar ones worth recommending.
This is what we’ve got so far:

Time to decompose!
Now that we’ve mapped the system input, processing, and output it’s time to break each part apart. Because in real life, none of these boxes exist in isolation. Each one hides a dozen small questions waiting to be asked.
Let’s start with the input. We know it’s “the posts the user has interacted with.” But where does that information actually come from? Do we already track interactions in a way that’s structured enough to use here?
If not, we’ll need to create a mechanism (that’ll be a completely different task) to collect, store, and query them efficiently. Sometimes, decomposition starts with realizing what isn’t there yet. In our case, let’s say that we already have a function in our user called interacted_posts that will return a list of posts user interacted with.
Then comes the output: Our list of recommended posts. But before a single line of code, we need to know what that output should look like. What format? What fields? Do we need to align with design or frontend to ensure it fits visually on the interface? Does it include just titles and links, or images and excerpts too? Every output is an agreement between systems and between teams.
Finally, the processing. This is where the hard logic lives: how do we find similar posts? Do we already have an interface that can infer relationships, or do we need to build it? Maybe this becomes its own problem a smaller one we can solve through another process of identification and decomposition. For now, let’s simplify and imagine we have a function called find_similars(). It’s a placeholder for the kind of complexity we’ll eventually face again and decompose again.
And of course, we’re not covering every possible scenario here, that’s not the goal. Each person, each team, and each product will ask different questions and define different levels of detail. A content management system doesn’t need the same level of rigor as a railway control system. The art of decomposition is knowing how deep to go and when to stop digging.
That’s how decomposition grows recursively: every problem contains smaller ones that deserve their own moment of clarity.
After decomposing a little, we’ll have this:

So, this is our current picture: a clean, structured view of a problem that originally arrived as a single vague sentence. We now have context, dependencies, and direction. Each arrow hides a set of decisions, but they’re no longer invisible.
That’s the real power of decomposition: it doesn’t actually make problems smaller, it makes them visible and feasible. Once you can see the moving parts, you can reason about them, estimate, delegate, improve, and code a solution.
From this point, the solution could evolve in countless ways, across an infinite array of possibilities with different languages, frameworks, and architectures. The data model might change, the similarity logic might grow into a machine learning model, or the output might connect to a new interface.
But what probably won’t change (unless the expected outcome does) is the path of that problem’s resolution, the learning you acquire while decomposing, and the visibility you gain into how things actually work.
Closing the Loop
The real output of decomposition isn’t code, it’s understanding. When we decompose a problem, we turn complexity into visibility, and visibility into direction. That’s what allows ideas to leave the abstract world of “what if” and enter the concrete space of “what next.”
Decomposition doesn’t guarantee success, it guarantees understanding, and that’s often the real win.
In the next piece, we’ll talk about prioritization, what to do first, how to strengthen collaboration across teams, how to deliver fast while still building better solutions, and how to unblock others so the whole system keeps moving forward.