The Simplest System That Solves the Problem Is Almost Always the Right One

by Eric Hanson, Backend Developer at Clean Systems Consulting

The Bias Toward Complexity

Engineering culture has a bias toward complexity. Complex systems are more interesting to build. They signal technical sophistication. They fill architecture diagrams in ways that simple systems do not. A system with a message queue, a service mesh, a distributed cache, and six microservices feels more "serious" than a monolith with a PostgreSQL database.

This bias is expensive. Complex systems have more failure modes, more operational burden, more surfaces for bugs, and more things for engineers to learn and maintain. Every piece of complexity should be there because it is solving a specific problem that simpler alternatives cannot. Complexity that exists for its own sake — or to signal technical maturity — is waste.

The simplest system that correctly solves the problem is not a starting point you iterate away from. It is often the destination.

What Simplicity Actually Means

Simplicity is not the same as naive. A simple system is one where every component and design decision can be justified by a specific problem it solves. It is not a system with fewer features. It is a system without components that do not earn their presence.

A PostgreSQL database with proper indexing and connection pooling is simple. It is also capable of handling most application data requirements up to significant scale. Adding a distributed cache, a sharding layer, and a read replica before you have evidence that the PostgreSQL instance is the bottleneck is not an upgrade to the simple system — it is complexity without justification.

A monolith deployed to two instances behind a load balancer is simple. It can handle tens of thousands of daily active users. Splitting it into 12 microservices before you have team-scale deployment bottlenecks or dramatically different scaling requirements is complexity without justification.

# The simplicity test for any proposed component:

1. What specific problem does this component solve?
2. Has that problem manifested in the current system?
3. What is the simplest alternative that addresses it?
4. Why is this component better than the simpler alternative for this specific case?

If you cannot answer all four questions clearly,
the component has not earned its place in the design.

The Cost of Each Layer of Complexity

Every layer added to a system has ongoing costs that compound:

  • Operational knowledge: engineers must understand the component to operate it correctly
  • Failure modes: each component can fail, and its failure mode must be understood and handled
  • Testing surface: integration tests must cover interactions with the new component
  • Debugging overhead: production issues now span more components, requiring more context to diagnose
  • Onboarding cost: new engineers must learn the system including this component

These costs are invisible in the architecture diagram but very visible in the engineering team's time. A system that requires 40% of engineering time in operational maintenance is one where 40% of engineering time is not going into the product.

When Complexity Is Warranted

Complexity earns its place when a simpler alternative cannot meet a specific, validated requirement. The requirement must be current — not anticipated. The failure of the simpler alternative must be demonstrated — not assumed.

Read replicas earn their place when the primary is demonstrably the read bottleneck. Microservices earn their place when independent deployment becomes a demonstrated bottleneck or when scaling requirements are demonstrably different between domains. A message queue earns its place when synchronous processing demonstrably blocks the critical path or when retry and durability requirements cannot be met without it.

The word "demonstrably" is doing a lot of work here. Demonstrably means you have data — performance metrics, incident postmortems, throughput numbers — that shows the simpler approach failing. Not "we think it might fail at scale." Actual failure.

The Practical Implication

When designing a system, start by asking: what is the simplest possible implementation that correctly handles the known requirements? Build that. Measure it. When it fails a specific requirement — not when you anticipate it might — add the complexity that addresses that specific failure.

This is not an argument against ambition. It is an argument against paying complexity costs now for problems you have not yet confirmed you have. The simplest system that works is almost always easier to operate, easier to debug, easier to scale from, and easier to change than a complex system designed for imagined requirements.

Build what solves the problem. Nothing more.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Why Your API Returns 200 Even When Something Goes Wrong

Returning HTTP 200 for failed operations hides errors, breaks client logic, and makes systems harder to debug. Using proper status codes is not pedantry—it’s critical for correctness and reliability.

Read more

Spring Boot Caching in Practice — @Cacheable, Cache Warming, and When Caching Makes Things Worse

Spring Boot's caching abstraction makes it easy to add caching to any method. What it doesn't tell you is when caching the wrong things causes stale data bugs, cache stampedes, and memory pressure that's harder to debug than the original performance problem.

Read more

The Real Cost of Hiring a Backend Developer in Barcelona Once You Add Employer Contributions

The salary on the offer letter is only part of what a Barcelona backend hire actually costs. Most founders find out the rest after they've already committed.

Read more

Handling Scope Creep Without Losing the Project

Criticism stings, even when you know it’s supposed to help. Learning to handle it without losing confidence is a superpower for any professional.

Read more