TSIBERSoftware architecture
All articles

Four types of software complexity

Every team says the system is too complex. Almost no team says which kind of complexity it has, and the four kinds need opposite treatments.

Every team says the same sentence: this system is too complex. Almost no team unpacks it. The phrase compresses several different problems into one complaint, and once that happens, the wrong remedy gets applied to the wrong problem — usually with energy and conviction.

Software complexity is not one thing. Brooks split it in two forty years ago: essential and accidental. After twenty years across banking, enterprise systems and AI infrastructure, two categories stopped being enough. Here are four.

Business complexity

The irreducible weight of the problem itself. Banking systems do have regulatory constraints. Insurance products do have edge cases. A logistics system genuinely has to model warehouses, routes and timing. You cannot simplify reality away.

Two things are worth knowing about this category. The first is that it is almost always smaller than it looks — teams discover that less of the system is essential than they assumed. The second matters more, and is skipped more often: business requirements are not set in stone. In most cases you can offer the business two or three alternative shapes of the same outcome, and one of them costs a fraction of the others to build. That conversation saves more complexity than any refactoring will.

Strategy: accept what belongs to reality, spread it evenly across the system, and periodically check whether the constraint is still a constraint.

Tool complexity

The complexity your tools bring with them. Spring Boot is not your business problem. Kubernetes is not your business problem. Unity is not your business problem. But once chosen, a tool becomes structural: its assumptions shape everything built on top of it.

This complexity carries no business value and cannot be removed after the choice is made. It is reality as given. You route around it; you do not fight it. Most of the pain in this category comes from teams that picked a tool and then refused to work the way that tool expects — paying for the framework twice, once in licence and once in resistance.

Strategy: choose deliberately, then commit far enough to collect the tool’s strengths instead of arguing with its design.

Accidental complexity

Complexity that exists because somebody could not see the simpler path. No business value whatsoever, and usually the largest share of the total.

It has one dangerous property that shapes everything about how you find it: the person who created it cannot see it. Not will not — cannot. From inside, every abstraction felt reasonable at the moment it was added. A team adds a temporary layer to keep options open, then another to compensate for the first, and eventually nobody can hold the system in their head. Each step was locally sensible. The sum is not.

That is why code review exists, and often why it fails: a reviewer who shares the author’s assumptions shares the author’s blind spot.

Strategy: bring in someone with no emotional investment in the current architecture. Fresh eyes find accidental complexity far more reliably than insiders do, and reasoned disagreement is worth more than agreement — ten words of why not beat two pages of why yes, because disagreement opens assumptions and agreement cements them.

Missing-tool complexity

Complexity caused by the absence of a tool that should exist and does not. This is the most underestimated of the four, and the one whose economics just changed.

Before AI, internal tooling was expensive enough that organisations normalised the pain instead of solving it. Repetitive manual work, awkward handovers, operational friction — accepted as the weather. The cost was never questioned because the alternative was a quarter of engineering time.

That assumption no longer holds. Unity Bridge took an evening to build and turned “AI cannot operate the Unity Editor” into a fifteen-minute workflow. The same pattern showed up inside banking environments: a missing internal tool, built quickly, replacing what had previously required months of process. And the effect compounds — each tool you build makes the next one cheaper, because it becomes part of what you build with.

The complexity was never in the work. It was in the absence of the right tool.

Strategy: when a team keeps suffering through the same painful workflow, ask whether the missing tool could now be built in days instead of quarters. Increasingly the answer is yes.

The four, side by side

The categories are not a taxonomy for its own sake. They differ along exactly the two axes that decide what you should do next.

KindCan you see it?Can you remove it?What works
BusinessYesNo — but it can be spreadAccept it, distribute it evenly, re-check the constraint
ToolYesNo, once chosenName it, route around it, stop fighting the design
AccidentalNo — the author is blind to itYesAn outsider, and disagreement that is argued
Missing toolNo — it is taken for the weatherYesAsk what tool should exist, then build it

Two readings fall out of the table.

Only one of the four can be deleted outright. Accidental complexity is the single category where removal is almost pure upside — and removing it tends to produce positive side effects nobody predicted. Business and tool complexity cannot be deleted at all. But they can be spread: a thin layer across the system instead of a dense lump in one place. That is most of the craft. An architecture is not judged by how much complexity it contains but by how evenly it is distributed.

The two dangerous kinds are the invisible ones. Accidental complexity hides because its author believes it is the simplest possible solution. Missing-tool complexity hides because the pain has been accepted as a fact of life. Neither announces itself in a retrospective. Both need someone from outside, or a deliberate question, to become visible at all.

One mechanism turns the manageable kinds into the removable kind: mixing layers manufactures accidental complexity. Business rules interleaved with transport, transport interleaved with storage — each blend is new complexity that belongs to nobody’s problem domain. Layered architecture is usually taught as a best practice. It is better understood as a consequence: separate the layers and the accidental share drops by itself.

And a rule of thumb that holds up across projects: focus on tools increases complexity, focus on the business decreases it. A team that talks mostly about its stack is growing the second and third categories. A team that talks mostly about the customer’s problem is shrinking them.

Every engineer has a thermostat

Here is what the four categories do not explain on their own: why two competent engineers, looking at the same system, disagree about whether it is too complex.

Every engineer has a comfortable level of complexity, and transforms the task until it reaches that level. A low thermostat simplifies — its superpower is finding the simplest model underneath; its failure mode is losing something that mattered. A high thermostat builds up — it can carry a heavy load without strain, and it grows accidental complexity without noticing.

That last part is the useful bit. A high thermostat has a dead zone: small additions fall below its threshold of perception. Like a scale that starts measuring at a kilogram, it registers nothing as grams accumulate — and they do accumulate. This is why systems get heavy without any single decision anyone would defend as a mistake.

The dead zone explains something else: teams with uniformly high thermostats produce sophisticated architectures that nobody outside the team can maintain, and they are genuinely surprised when the next hire takes four months to become useful.

The best architecture comes out of two thermostats working against each other — one pulling up (you are dropping something you will need), one pulling down (you are adding something nobody asked for). Not as a review stage bolted on at the end, but as a live argument during design. Teams that staff themselves for agreement lose this instrument entirely.

It is worth noticing how complexity actually registers before it can be measured. It arrives as a loss of harmony: the fan of possible next actions narrows, and the landscape closes in. Simplification feels like the opposite — options reopen, movement becomes cheap again. That sensation is not mysticism; it is a fast reading of how many moves the system still permits. It arrives long before any metric does, and it is worth trusting enough to investigate.

This applies to AI assistants too, and rather sharply. Their training data is enterprise patterns, abstraction layers and design patterns, so their thermostat sits high by default. Ask one for an architecture and you will get a competent, heavier answer than the problem required. Knowing that is most of the correction.

A fifth candidate: language

There is one more, and it is underrated to the point of near-invisibility: the complexity of natural language itself.

Language is imprecise. It is hard to express a thought exactly, and harder to recover which meaning the speaker actually put into the words. A requirement that is understood in two ways produces two systems, and the discrepancy surfaces months later as a defect nobody can attribute. A great deal of what gets classified as accidental complexity started as a sentence that two people read differently.

It has a compensation rather than a cure: carry the meaning in more than one medium. A diagram, a sketch, a metaphor, a worked example beside the prose. Multimodality does not make language precise — it reduces what is lost in transfer, which is the part you can actually control.

Two working habits follow from it. Check understanding, not execution: there is little point supervising how someone implements, and a great deal of point in asking them to describe the problem they think they are solving — the answer exposes the gap while it is still cheap. And stop specifying at eighty percent: a specification cannot be completed to a hundred, does not need to go past eighty, and the remaining fifth is cheaper to discover in flight than to predict in advance.

Why this matters

Teams apply the wrong strategy because they misclassify the problem. They refactor when the real issue is a missing tool. They argue about frameworks when the issue is accidental layering. They try to simplify business rules that are carrying essential domain knowledge. The result is wasted effort and, more often than not, more complexity than they started with.

So when someone says this system is too complex, the useful question is no longer “how do we simplify it?”

The useful question is: which kind of complexity are we actually dealing with?

In my experience a surprising share of modern engineering pain turns out to be the fourth kind — and most teams still do not know that category exists.

The first step is a diagnostic: two weeks, CZK 60,000 (about €2,480), fixed. You leave knowing what the system does today, what blocks change, and what to do first. What I need from you: access to the code and to the running system, and an hour each with two or three people who own it. The two weeks start on the day that access is open.

Services
Callback

I will call you back

Leave a number and one line about the system. I call and ask what I would have asked by email.

The number is used for this call only.