Complexity

Essential complexity

All systems are complex because all systems have essential complexity. Essential complexity is the complexity that is inherent in the [problem] that the system is trying to solve, or the domain of the software.

For example, a system that is trying to solve the problem of routing packets across a network will have to deal with the inherent complexity of routing algorithms, network topologies, and so on.

You can’t eliminate essential complexity in a system, but you can reduce it by reducing the scope of the problem you’re trying to solve.

Accidental complexity

Accidental complexity is the type of complexity in software systems that developers have more control over. This is the complexity that comes from the decisions the developers choose to make in the system design.

Different architectural styles, design patterns, programming paradigms, application frameworks, and runtime environments – all of these things make trade-offs between competing [qualities], and therefore bring different types of accidental complexity into a solution.

For example, distributed software is inherently more complex than monolithic software. A distributed program has to deal with the complexities of network communication, data consistency, and fault tolerance. Add asynchronous inter-process communication to the mix, and you have even more complexity that requires specialist tools and techniques – like observability and especially distributed tracing – to deal with.

The three laws of software complexity

Mahesh Balakrishnan’s 2024 essay "Three Laws of Software Complexity" argues that engineers are destined to wallow in unnecessary complexity because of three forces that, between them, explain why most working software is badly designed regardless of how carefully it started out.

  • First law: a well-designed system degrades into a badly designed one over time. Taking "well-designed" to mean "easy to change", every quick and easy change to a well-designed system risks leaving it harder to change next time. A well-designed database behind a clean storage engine API stays swappable until someone adds a getLevelSize call, at which point it can no longer be ported off an LSM tree. Well-designed is therefore an unstable, ephemeral state, and software rot is the stable attractor the whole field drifts toward. This is technical debt accruing as a law of motion, not a one-off mistake. Lehman’s laws of software evolution named the same dynamic decades earlier.
  • Second law: complexity is a moat, filled by leaky abstractions. When systems compete for adoption, the pressure to attract application developers pushes designers to expose implementation detail through the API. This both grows market share and locks the implementation in, because no competitor can substitute a different one under the hood. ZooKeeper’s stronger-than-linearizable consistency coupled to TCP/IP-based ephemeral node semantics, and Kafka’s idempotent produce semantics, are APIs that are nearly impossible to implement any other way. This is leaky abstractions weaponized as a competitive strategy rather than suffered as an accident.
  • Third law: there is no fundamental upper limit on software complexity. In systems built by large groups over time, complexity is bounded only by human creativity. The shape of a system reflects the abilities, philosophies, and incentives of dozens of developers, each navigating real and perceived political constraints. Why does a replicated database roll its own gossip failure-detection layer instead of relying on Kubernetes? Maybe the original author’s PhD was on gossip, maybe the manager had already written the promo docs, maybe the tech lead split membership and leader election into separate layers to avoid a political fight. Each existing system is, in Balakrishnan’s phrase, "a DoS attack on you by dozens of people you may not even know" – a booby-trapped palace of ticking complexity time-bombs planted years before you arrived.

The throughline is that conceptual integrity is the quality the three laws conspire against: a single coherent design erodes under accumulated changes, competitive API pressure, and the many hands that touch a long-lived system. Balakrishnan’s own answer is to keep building new systems from scratch before they succumb – a strategy whose difficulty he concedes.

See also

  • Abstraction: The mechanism the second law exploits.
  • Computer science
  • Gall’s law: Complex working systems evolve from simple ones, never from scratch.
  • Hock principle: The organizational counterpart — simple purpose and principles give rise to complex behavior.
  • KISS principle: The design principle that names simplicity as a goal and accidental complexity as the enemy.
  • Silver bullet: Brooks’s argument that no single technique can deliver an order-of-magnitude gain, framed on the essential/accidental distinction above.
  • Tesler’s law: The complexity an application carries can only be shifted between the developer and the user, not eliminated.