Why I Stopped Over-Engineering and Started Shipping

by Arif Ikhsanudin, Backend Developer

Over-engineering feels like doing your job well. It isn't. Here's how I learned to tell the difference between building something robust and building something expensive for no one.

The System Nobody Used

Early in my career, I spent three weeks designing a configuration system for a backend service. It was elegant. It was extensible. It supported multiple environments, multiple override levels, hot-reloading without restarts. I was genuinely proud of it.

The service it was built for handled about forty requests a day. It never scaled. It was eventually deprecated. The configuration system went with it, never having justified a single hour of the time spent building it.

That's over-engineering in its purest form: building for hypothetical futures that never arrive, at the expense of the actual present.

Why Smart People Over-Engineer

It's not stupidity. Over-engineering is almost always a symptom of intelligence applied in the wrong direction.

There are a few recurring causes I've noticed in myself and others:

Boredom with the simple solution. The right answer is often boring. A flat config file, a simple queue, a single database with a few indexes. Implementing the boring thing doesn't feel like engineering — it feels like settling. So you reach for something more interesting.

Anxiety about future criticism. If you build the minimal thing and it breaks under load or needs to be replaced in six months, that's a visible failure. If you over-build, the failure is invisible — it's the time you wasted, the complexity you added, the team you slowed down. Nobody puts that on a postmortem.

Mistaking complexity for quality. Systems that look sophisticated feel safer. More layers, more abstraction, more indirection — surely that's a sign of a well-designed system? Sometimes. Often not.

The Real Cost of Over-Engineering

The most obvious cost is time. Three weeks on a config system that should have taken three days.

But the less visible costs compound over years:

  • Cognitive overhead. Every abstraction layer is something the next developer has to understand before they can change anything. Complexity multiplied across a team becomes a significant drag on velocity.
  • Maintenance surface. More code means more things to update, more things to break, more things to test. The system that runs on three moving parts is inherently easier to keep running than the one that runs on twelve.
  • Premature optimization of the wrong things. When you design for scale you don't have, you often optimize the wrong bottlenecks. You spent six weeks building a distributed caching layer, and the actual problem turned out to be a missing database index.

The Shift in How I Think About Design

I started asking a different question when approaching any new design decision: what's the minimum system that solves the actual problem in front of me — not the imagined future one?

This isn't anti-engineering. It's discipline. There's a difference between a simple solution and a sloppy one. Simple means no unnecessary moving parts. Sloppy means you didn't think about it hard enough.

Some rules of thumb I've developed:

  • If I can't explain why this complexity is necessary right now, it probably isn't
  • "We might need this later" is not a requirement; it's a guess
  • Extensibility points that nobody uses are just dead weight with a philosophical justification
  • The best architecture is the one your team can understand and operate without you in the room

Shipping Is Information

There's a pragmatic argument for simplicity that goes beyond aesthetics: you learn the most from what actually runs.

When you over-engineer before you ship, you're making dozens of architectural decisions based on assumptions. You assume the traffic pattern will look a certain way. You assume users will need this feature. You assume the bottleneck will be here, not there.

Shipping breaks assumptions. The thing your users actually do with your product is different from what you imagined. The thing that actually falls over under load is not the thing you hardened. The feature you spent a month building extensibly never got a second use case.

The shorter the loop between building and deploying and learning, the better your next design decision will be. Over-engineering lengthens that loop.

What I Do Instead

I build the simplest thing that works, and I make sure it's built cleanly enough that extending it later isn't painful. That's a narrower requirement than it sounds:

  • Clear naming and module boundaries so the next change is easy to locate
  • Sensible error handling so failures are diagnosable
  • A test suite that covers the real behavior, not the implementation details
  • A README that explains what it does and why, not how

Then I ship it. I watch what happens. I make the next decision based on what I actually know, not what I predicted.

Most of the time, the simple thing is enough. Occasionally it isn't, and I add complexity where it's genuinely needed, with the benefit of real data to justify it.

That three-week config system would have been a three-day job, and the service would have shipped faster, and nothing about the outcome would have been worse.


The best code you ever write is the code that solves the problem in front of you and gets out of the way.

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

Australia's Backend Talent Pool Is Tiny Compared to Demand — Remote Contractors Close the Gap

You've been looking for a backend engineer for two months. The recruiter keeps sending frontend developers who "also do Node." That's not the same thing.

Read more

The Machine Behind My Backend Systems

This is the setup we use to deliver backend work that’s fast, reliable, and efficient. Optimized tools help us build systems anywhere, anytime, without compromise.

Read more

How to Handle a Client Who Wants to Pay Less Than Your Rate

A client pushing back on your rate is not automatically a problem. How you respond determines whether you end up with a better deal, an adjusted scope, or a politely declined engagement.

Read more

Forced In-Person Work: When Contractors Are Treated Unfairly

“We require all contractors to be onsite five days a week.” That sentence often signals a deeper misunderstanding of what contracting actually is.

Read more