Every engineering system starts simple.
Over time, complexity accumulates — sometimes from real requirements, sometimes from premature abstraction, sometimes from accumulated caution. The result is a system that is harder to change than it needs to be.
The discipline I try to apply is this: every piece of complexity in a system should have earned its presence by solving a specific, demonstrated problem.
What unearned complexity looks like
Unearned complexity has several characteristic signatures:
Over-abstraction at the beginning. The team designs for the fifth use case before they have built the first one. The abstraction is technically elegant and practically premature. When the requirements arrive, they are different from what was anticipated, and the abstraction does not fit. Changing it requires unraveling a design that was not written for change.
“We might need this later.” Features and infrastructure added because they seemed likely to be needed. Sometimes this intuition is correct. Often it is not, and the system carries the weight of preparation for a future that never arrived.
Platform-izing too early. Internal systems rebuilt as platforms before there are multiple real consumers. A platform without multiple real consumers is not a platform — it is a service with a complicated API and a conceptual overhead that nobody needs yet.
The wrong layer for the problem. Solving a product problem with infrastructure, or solving an infrastructure problem with policy. Complexity at the wrong layer does not reduce complexity. It relocates it to a place that is harder to reason about.
What earning complexity means
Earning complexity means that when you add a piece of abstraction, a new service, a new dependency — you can point to a concrete problem it solved and explain why simpler alternatives were insufficient.
This is not a prohibition on complexity. Some systems are genuinely complex, and they should be. Distributed consensus, multi-tenant data isolation, real-time synchronization — these domains have inherent complexity that cannot be designed away. Earning complexity is not about avoiding hard problems. It is about not adding hard problems where easy ones would have sufficed.
The test I find useful: can you explain why the simpler version was not good enough? If you cannot, the complexity may not have been earned yet.
The relationship to reversibility
Earning complexity also means thinking about what happens when the complexity turns out to be wrong.
A complexity that can be removed in a day has a different risk profile than a complexity that is woven through the entire architecture. Prefer architectures where the complexity is localized and reversible. When complexity must be deep and structural, make sure the problem it solves is equally deep and structural.
The most expensive systems I have seen are the ones where complexity was added in response to hypothetical scale, before the team had validated that the product would reach that scale. Some reached it. Many did not. The complexity remained either way.
The consequence of discipline
When a team practices this consistently, the codebase has a quality that is hard to describe but easy to recognize: everything in it is there for a reason that can be explained.
That explainability is not just aesthetic. It is the difference between a system that a new engineer can reason about in a week and one that takes six months to understand well enough to change safely.
Simple systems are also faster to change. The engineering leverage of a team working in a simple system is higher than the same team in a complex one.
Earn it. Make the complexity prove itself before it stays.