Software Engineering 4 min read

Your Codebase Isn't Complicated by Accident

When software becomes slow, fragile, or painful to change, we usually blame the code. At BSC 2026, Casey Muratori pushed the argument one level deeper: the real culprit may be an engineering culture that treats complexity as inevitable.

Complexity Arrives One Reasonable Decision at a Time

“Premature optimization is the root of all evil” is one of programming’s most quoted lines. Muratori’s title, “Root of the Root of All Evil,” asks what happens before teams even reach that debate.

Most complicated systems do not begin with obviously bad decisions. A team adds an abstraction to reduce duplication. It introduces an interface for future flexibility. Then come a framework, a code generator, and a few tools intended to make collaboration easier.

Each choice can sound sensible in isolation. The trouble begins when they accumulate.

Imagine changing one button requires opening 12 files across five layers. Every individual function may be tidy, yet understanding the full behavior is anything but simple.

Complexity is not primarily about lines of code. It is about the number of relationships a developer must keep in their head at once.

Abstraction Still Sends an Invoice

Abstraction is one of software engineering’s favorite answers. Hide the messy internals and expose a clean interface.

But hidden complexity has not disappeared. It is merely waiting for something to break.

A checkbox that refuses to update might be caused by UI code. Or the state-management library. Or a data transformation layer. Or a network cache. Or generated code that nobody on the team normally reads.

At that point, the abstraction stops being a shortcut and becomes a maze. When a stack trace runs beyond 30 frames, the first question is no longer “Where did this fail?” It is “What actually ran?”

The answer is not to abolish abstraction. It is to price it honestly. Does this layer remove more complexity than it introduces?

That question is often skipped because the benefits appear immediately, while the cost arrives later through debugging, onboarding, and change coordination.

The Organization Often Rewards the Wrong Work

Engineering culture has a habit of confusing elaborate design with expertise. A reusable framework looks more impressive than a small function. The person who introduces a new system may receive more recognition than the person who removes a dependency.

Performance reviews reinforce the pattern. A new feature fits neatly into a quarterly accomplishments document. Cutting build time from 10 minutes to 3 minutes or deleting an unnecessary layer is harder to showcase, even when it saves the entire team hours every week.

The incentives become lopsided. Engineers are rewarded for adding capabilities, but rarely for shrinking the amount of system everyone must understand.

Teams then borrow short-term convenience against future comprehension. Development feels faster at first. A few years later, a minor change requires design meetings, migration documents, expanded test plans, and deployment choreography.

Calling this “technical debt” does not quite capture it. Debt implies that everyone recognizes the liability. This is closer to collective numbness to complexity.

Simplicity Needs Metrics Too

“Keep the code clean” is not an operating model. If a team wants to defend simplicity, it needs to measure the costs developers actually experience.

Useful signals include:

  • Time from a new hire’s first day to their first production change
  • Number of modules affected by a small code edit
  • Build and test duration
  • Time required to identify an incident’s root cause
  • Total dependency count
  • Number of services, configuration files, and teams involved in a routine release

One especially revealing metric is change amplification. If one product requirement forces edits in 20 places, including tests and configuration, the architecture is telling you something.

Good design does not attempt to predict every possible future. It solves today’s requirement clearly while leaving code that tomorrow’s developer can read and modify.

Adding a layer when it becomes necessary is usually manageable. Removing one after it has spread across a codebase is much harder.

This Is an Old Argument, Not a New Bandwagon

It would be misleading to manufacture a wave of community consensus around Muratori’s talk. A review of Reddit activity during the previous 30 days in August 2026 found zero relevant discussions. There is no meaningful pile of upvotes or comments to present as the industry’s verdict.

The underlying disagreement, however, is familiar across Hacker News threads, conference hallways, and architecture reviews: real-world problems are complex, so their software cannot always be simple.

That is true. But essential complexity is not the same as complexity added by tools, conventions, and organizational habits. The first comes from the problem. The second comes from accumulated choices.

That distinction is why Muratori’s provocation matters. Bad code can be rewritten; a culture that treats complexity as evidence of sophistication is much harder to refactor.

Before adopting the next framework, platform, or architectural pattern, ask whether it makes the problem simpler or merely makes the complexity look familiar. Then ask the more uncomfortable question: who does your team value more—the engineer who masters a complicated system, or the one who makes that mastery unnecessary?

Software Engineering Engineering Culture Casey Muratori

Comments

    Loading comments...