Skip to main content
Engineering10 min read

The Case Against Over-Engineering (From Someone Who's Done It)

I once built a plugin architecture for a system that never needed plugins. 3 weeks of abstraction layers for a feature nobody asked for. Here's how I learned to stop.

By Jason TeixeiraDecember 1, 2025
ArchitectureOver-EngineeringYAGNIBest PracticesDesign
Share:
On this page

I have a confession. In 2023, I spent three weeks building a plugin system for a test automation framework. Configurable test runners. Hot-reloadable plugins. A dependency injection container. The whole thing.

Nobody ever wrote a plugin.

The framework ran in CI with the same configuration every time. The "extensibility" I built was used by exactly zero people. I could have shipped the entire thing in 4 days without the plugin architecture.

How Over-Engineering Happens

It starts with a reasonable thought: "What if we need to extend this later?"

That thought is the trap. Because "later" rarely looks like what you imagined, and the abstractions you build for imaginary requirements usually get in the way of the real ones.

Here's the progression I've watched in myself:

  1. Build a simple function ✅
  2. Think "this should be configurable" ⚠️
  3. Add a config object
  4. Think "different environments might need different implementations" ⚠️
  5. Add an interface and factory pattern
  6. Think "we might need to swap this at runtime" 🚩
  7. Add dependency injection
  8. Realize nobody has ever needed to swap it
  9. Maintain the abstraction forever because removing it is harder than keeping it

The Three Questions

Before adding any abstraction, I now ask:

1. "Has anyone actually asked for this?"

If the answer is "no, but they might" — don't build it. YAGNI (You Aren't Gonna Need It) is the most violated principle in engineering.

2. "What's the cost of adding this later vs now?"

If I can add the abstraction in 2 hours when it's actually needed, there's no reason to build it now "just in case." The cost of premature abstraction (maintaining code nobody uses) is almost always higher than the cost of adding it later.

3. "Can I explain why this exists to someone in one sentence?"

"We use dependency injection because we need to swap the payment provider between Stripe and Braintree in different environments." That's a real reason.

"We use dependency injection because it's best practice." That's not a reason. That's cargo culting.

What Simple Code Looks Like

\\

Reader route

article -> proof -> offer

ReadClusterProofScope

cluster

Product Systems

intent

Engineering

route

next step

What to do with this

Turn the note into a build path.

If this topic maps to a real business problem, keep reading the cluster, study the academy path, or route the work into a scoped engagement.

Jason Teixeira
Written by
Jason Teixeira
Founder, Sage Ideas Studio · Principal Engineer
livebuild a1556e22026-06-19 03:29Z
// solo studio// no analytics resold// every commit human-reviewed