A straightforward design begins with understanding a difficult problem.
Some systems are surprisingly easy to describe. You can follow an operation, find the important decisions, and explain what happens when something goes wrong. The implementation might contain a considerable amount of work, but its structure makes that work understandable. I think this is one of the most valuable qualities software can have.
Reaching that kind of simplicity requires a deeper understanding of the problem than we usually acknowledge.
A small implementation can be small for very different reasons. It might capture the requirements directly and avoid machinery the system does not need. It might also leave important requirements undefined. Both can look straightforward when everything succeeds.
Imagine an operation that records an order and sends a message to begin fulfillment. Save the order, publish the message, return a response. But the process can stop between the first two steps. Now the order exists and fulfillment has never been told about it.
If completing that handoff is part of our promise to the customer, the system needs a way to recognize and recover from that situation. Recording the order and the unfinished handoff in the same database transaction gives a worker something it can retry after a crash. That adds code, and repeated delivery still needs to be safe. But those decisions give a failure case an explicit answer. Removing them would shorten the implementation while leaving someone else to discover which orders never reached fulfillment.
We can make almost anything look simple by leaving enough of the problem unresolved.
In earlier posts, I wrote about boundaries, layers, queues, and coordination. Each can solve a real problem. Each can also introduce obligations the system would otherwise avoid. Engineering has to understand which complexity belongs to the problem and which complexity we have introduced through our decisions.
Pricing can depend on agreements, dates, regions, and exceptions. Payments can succeed while a response is lost. Several customers can compete for the last item in stock. The system has to represent those realities clearly enough to handle them. Moving the rules into another service does not make them disappear.
But we can also introduce several copies of the same state, several places where it changes, and several mechanisms intended to keep them aligned. Sometimes the requirement justifies that arrangement. Sometimes a more direct solution would have been sufficient.
Choosing the direct solution confidently requires evidence.
Consider a cache. It can make a repeated read cheaper, but it also introduces another copy of information and decisions about how long that copy remains useful. When it differs from the source, the system needs an answer for what the user is allowed to see.
If the original read already fits comfortably within the application's latency and capacity requirements, the cache might buy very little. Measuring demand, read cost, and the variation the system needs to handle lets us make that judgment. Keeping the read direct becomes a decision we can defend because we understand the workload.
This is where physics, engineering, and architecture fit together. Physics gives us constraints: execution capacity, communication latency, bandwidth, and the costs of moving data. Engineering examines those constraints alongside business guarantees, reliability, and human limits. Architecture expresses the decisions that follow.
That can mean keeping an operation within one database so we do not have to coordinate several of them. It can mean separating a workload because resource isolation makes the rest of the system more predictable. The simplest overall arrangement can require more structure in one place and less in another.
I think this is why simplicity is difficult to judge by looking at a single component. We can make a service look clean by moving difficult behavior into its callers. We can make an application look small by depending on a complicated manual recovery procedure.
The complexity still exists. Somebody has to carry it.
Good engineering accounts for the people who operate, change, recover, and use the system. Saving a little code while creating repeated uncertainty for everyone else can increase the total burden.
There is also simplicity in choosing scope deliberately. A universal abstraction can acquire many extension points before we understand the variations it is supposed to support. Implementing concrete requirements directly gives us a clearer basis for deciding what the next variation needs. Likely changes still deserve preparation, especially when a decision is difficult to reverse. But flexibility has a cost from the moment we introduce it.
Adding structure is easier to recognize than establishing why it is unnecessary. A new service appears in the diagram. A new framework appears in the repository. An investigation showing that the existing design meets the requirement might leave very little visible machinery behind.
The knowledge still matters. Understanding the workload and failure cases gives the team a reason to trust the simpler design. It also tells us when to reconsider it. Traffic, requirements, and ownership can change. Knowing why today's arrangement is sufficient helps us recognize which new constraint would justify changing it.
The more I work on systems, the more I feel that simple designs contain knowledge that is easy to overlook. Somebody understood which guarantees mattered, which work could wait, and which coordination was actually necessary. The result looks straightforward because those decisions removed problems the implementation would otherwise have to manage.
A simple system still has responsibilities. It still has limits. It still needs to behave sensibly when things fail.
Simplicity has to be earned by understanding those things well enough to give them a clear answer.
Top comments (0)