Why most engineers design systems too early
Why most engineers design systems too early, why this happens, and what to do instead.
There’s a moment that happens in almost every engineering team I have ever observed.
The product manager drops a new feature in Slack. Maybe it’s a notification system, a billing module, or a search endpoint. Before anyone has written a single line of code, before anyone has talked to a single user, an engineer opens a Miro board and starts drawing boxes.
Queues appear. Services multiply. A caching layer materialises from nothing. Within 20 minutes the whiteboard looks like a NASA mission control schematic and everyone is nodding seriously like this is good work.
It isn’t. It’s one of the most expensive habits in software engineering, and almost nobody talks about it.
The seduction of the blank canvas
Here’s why premature system design is so common: it feels productive.
Drawing architecture diagrams is fun. It’s creative. It’s social, other engineers gather around, contribute ideas, and argue about trade-offs. It produces an artefact you can point to in a meeting. It signals seriousness.
And there’s a subtler pull too. System design is where engineers feel genuinely expert. Writing a PRD, talking to users, scoping an MVP; those all feel fuzzy and uncomfortable. But a sequence diagram? A service topology? That’s solid ground. That’s our domain.
So we retreat to it. Early and often.
The problem is that good system design requires constraints, and early in a feature’s life, you have almost none. You don’t yet know the actual load. You don’t know the access patterns. You don’t even know which parts of the feature will actually be used. You don’t know if the feature itself will survive first contact with real users.
You’re basically designing a highway before anyone has confirmed there is a destination.
What over-designed systems actually cost
Let me be concrete about the damage, because it’s rarely visible until it’s compounding.
You build for scale you don’t have. I’ve seen teams spend 4 weeks engineering a message queue for a notification system that served 200 users. A database table and a cron job would have done the job, and been in production in 3 days. Instead, they’re babysitting Kafka at 200 users. That’s not technical sophistication. That’s overhead masquerading as foresight.
You commit to abstractions before you understand the domain. The worst kind of code to change is code that was abstracted too early. When you split services before you understand the boundaries, you get a distributed monolith: all the latency and operational complexity of micro-services, none of the independence. I’ve inherited systems like this. It’s archaeology. Every change requires excavating 4 repos and a shared library nobody fully understands.
You slow down iteration precisely when it matters most. The early phase of any product is when you need to move fastest. This is when you’re learning what the product actually needs to be. A complex system is a slow system to change. Every microservice boundary is a tax on velocity. Every abstraction layer is another place your hypothesis can get stuck.
You burn your team’s most creative energy on the wrong problem. The engineers who spent 4 weeks on that Kafka setup couldn’t use that brain time to understand why users were churning, or what the second feature should be, or whether this feature was even solving the right problem. Cognitive capacity is finite. Premature design eats it in large chunks.
The error underneath the error
There’s a thinking mistake that causes all of this, and it’s worth naming precisely.
Early-career engineers often conflate two very different questions:
What system do I need to build this feature?
What system will I need if this feature succeeds?
Question 2 is genuinely important. You do need to think about scale. You do need to avoid painting yourself into corners. But question 2 is only answerable with data, and you won’t have that data until you’ve shipped question 1.
Most premature system design is an attempt to answer question 2 before question 1 has even been asked.
The experienced engineers I most respect have a different instinct. When a new feature lands, their first question isn’t “how do we build this?” It’s “what do we need to learn?” They design the system that will teach them the most, as cheaply as possible. Then they design the next system with real information in hand.
This is not laziness. It’s a deliberate prioritisation of information over infrastructure.
The YAGNI principle isn’t enough
You’ve probably heard of YAGNI (You Aren’t Gonna Need It). The principle is sound: don’t build features or abstractions you don’t need yet.
But YAGNI is usually applied at the code level. “Don’t write a plugin system if you only have one consumer.” That’s correct, but it doesn’t go far enough.
The deeper version of YAGNI applies at the architectural level: don’t design a distributed system if you don’t have a distribution problem. Don’t introduce a message queue if you don’t have an async processing problem. Don’t add a caching layer if you don’t have a latency problem you’ve actually measured.
The inverse of YAGNI is what I’d call premature abstraction anxiety: the fear that if you don’t build for scale now, you’ll regret it later. This anxiety is often well-intentioned but it’s almost always miscalibrated. The cost of retrofitting a simple system to handle more scale is almost always lower than the ongoing cost of operating a complex system you didn’t need.
There are exceptions. A few architectural decisions are genuinely hard to reverse: your choice of primary database, your decision to go monolith vs service-oriented, your event schema format. These are worth thinking about early because they’re expensive to change later.
Everything else? Ship the simple version.
What to do instead: the three-question test
Before designing anything, I ask 3 questions. Not as a bureaucratic exercise, but as a forcing function to make sure I’m building the right thing at the right time.
Question 1: What do I need to learn, and what’s the cheapest way to learn it?
If you’re building a recommendation system, you don’t need a ML pipeline on day one. You need to know whether users click on recommendations. A hand-curated list served from a Postgres table will teach you that faster than anything. Once you know users click, you can invest in the infrastructure that serves them better recommendations.
Question 2: What’s the simplest system that survives being wrong?
Every early design will be partially wrong. The question is whether your architecture lets you recover when you discover the wrongness. Simple systems with clear data models and minimal service boundaries are easy to change. Complex systems are not. Design for changeability, not for the optimistic scenario.
Question 3: What would I regret not building if this scales?
This is the one concession to question 2 from earlier. There are a handful of decisions where future-you will thank present-you for thinking ahead. Data schemas. Observability hooks. Idempotency patterns. These are cheap to add now and expensive to retrofit. Build them. Build nothing else.
A story about a payment system
A team I worked with was building a checkout feature for a B2B SaaS product. They had maybe 50 customers, all on monthly invoices. The plan was to build a payment system from scratch: a billing system, a webhook handler, a retry queue, a dunning system, and a custom reconciliation module.
They estimated six weeks.
I asked them: how many payments would this system process in its first month? About 50, they said. What’s your current churn problem: is it payment failure or something else? They didn’t know.
So, we scrapped the plan. They integrated Stripe Billing in a week, wired up their three existing invoice states to Stripe’s webhook events, and shipped. Six weeks became one.
Three months later, with real payment data, they discovered that 80% of their failed payments were from a single enterprise plan that needed purchase order support: something their hand-rolled billing system wouldn’t have handled anyway. They built exactly that, and nothing more.
The complex system they almost built would have been a monument to solving problems they didn’t have.
The seniority signal
Here’s a useful diagnostic. When a new feature is being discussed in your team, watch who speaks first and what they say.
The junior engineers are often the first to propose the complex architecture. Not because they’re bad engineers, but usually because they’ve been reading about distributes systems and they’re excited to apply what they know. The pattern-matching fires early.
The most senior engineers I’ve worked with do something different. They ask questions. What’s the expected load? How many users in the first 90 days? What’s the worst thing that happens if this goes down? What’s the part of this feature we’re least certain about?
They’re collecting constraints before they commit to a design. They know that a system designed with constraints is always better than one designed without them. And they know that almost all constraints come from shipping something, not from thinking harder before you ship.
This is one of the most reliable signals of engineering maturity I know: the instinct to delay design commitments until you have enough information to make them well.
The permission you might need to hear
If you’re an engineer who has been told, either explicitly or implicitly, that building simple systems is a sign of low ambition, I want to offer a counter-argument.
Knowing when not to design is harder than knowing how to design. Anyone can draw boxes and add queues. It takes real judgement to look at a whiteboard full of services and say “we don’t need any of this yet.”
The engineers I’ve seen grow the fastest are the ones who got very good at asking: what’s the least I need to build to learn what I need to know? That question, asked consistently, produces better software and better careers than any design pattern I’ve ever encountered.
Ship the simple thing. Learn from it. Then design the right system, with the right constraints, for the right problem.
That’s the job.
Stack & Scale is a newsletter for engineers who want more from their careers. If this resonated, share it with one engineer who needs to hear it: it’s the best way to help this publication grow.

