Premature microservices anti-pattern
Understand why splitting a small application into microservices adds distributed systems complexity before you have the scale or team size to justify it, and when to actually make the switch.
TL;DR
- Microservices are the correct answer at scale and with large teams. At a three-person startup or in a new product domain, they are almost always the wrong answer.
- A monolith is not a failure. It is the correct architecture for services with unproven domain boundaries, small teams, and low operational maturity.
- The distributed systems tax (network latency, partial failures, distributed transactions, versioning, service discovery, observability) is real and must be earned by the scale that justifies it.
- The correct path: start as a modular monolith with clean interfaces, measure where you actually hit bottlenecks, then extract services surgically using the strangler fig.
The Problem
A startup of 4 engineers decides to build a SaaS product. They've read about Netflix and Uber. They architect 12 services on day one: Auth, User, Billing, Notifications, Analytics, Products, Orders, Shipping, Reviews, Search, Admin, and a BFF Gateway.
Six months in, adding a new feature requires coordinating changes across 4 services. They have a 2-hour deployment ceremony requiring all services to be updated in the right order. Their local development environment requires Kubernetes, 12 containers, and a service mesh to run. Debugging a bug requires reading distributed traces across 6 services.
Their competitor, a 2-person team with a Django monolith, shipped the same feature in an afternoon.
The startup's problem is not scale. They have 200 users. Their problem is they've prematurely imported the complexity model of an organisation with 2,000 engineers for a team that's too small to own that complexity.
I made this exact mistake in 2019. We had 3 engineers and 8 services. Our deploy pipeline took 45 minutes. The Django monolith we replaced used to deploy in 90 seconds.
A single feature that would be a one-line change in a monolith becomes a multi-service coordination problem.
Why It Happens
Premature microservices come from good intentions, misapplied.
"Netflix does it." Netflix has 2,000+ engineers. Their architecture solves coordination problems at that scale. At 4 engineers, you don't have coordination problems. You have a small team that can walk over to each other's desks.
"We need to be ready to scale." This assumes you know which parts will need independent scaling before you've found product-market fit. You almost certainly don't. Premature scaling decisions are premature optimization at the architecture level.
"Microservices enforce good boundaries." So do modules, packages, and interfaces. You don't need a network boundary to enforce a code boundary. A function call with a clean interface is just as decoupled as an HTTP call, without the network tax.
"We'll be stuck with the monolith forever." The strangler fig pattern exists precisely for this scenario. A well-structured monolith with clean module boundaries is easier to extract from than a poorly-structured microservices system is to consolidate.
The complexity cliff
The problem isn't that microservices are bad. The problem is that the complexity they introduce has a minimum cost regardless of system size. Whether you have 100 users or 100 million, you still need:
- Service discovery and health checks
- Distributed tracing and log aggregation
- Circuit breakers and retry logic
- API versioning and backward compatibility
- Integration testing across service boundaries
- A deployment pipeline per service
For a 200-person engineering org, this infrastructure already exists and is maintained by a platform team. For a 4-person startup, building and maintaining this infrastructure IS the product work. You spend 60% of your time on plumbing and 40% on features.
I've audited three startups that went microservices-first. In every case, the team estimated the infrastructure overhead at 10-15% of their time. The actual number, measured over 6 months, was 40-55%.
How to Detect It
| Symptom | What It Means | How to Check |
|---|---|---|
| More services than engineers | Over-decomposition | Count services vs team size. If ratio > 2:1, question every service |
| Local dev requires Kubernetes or Docker Compose with 5+ containers | Infrastructure overhead exceeds product complexity | Time how long docker-compose up takes. Over 5 minutes is a smell |
| Adding a simple feature requires PRs in 3+ repos | Cross-service coupling from premature splits | Track the average number of repos touched per feature over a sprint |
| Deployment requires a specific order or a coordination meeting | Deployment coupling | Ask: can each service deploy independently? If no, you split too early |
| Team spends more time on infra than product | Operational overhead exceeds product value | Track the ratio of infra tickets to product tickets over a month |
| Inter-service calls dominate latency | Network tax exceeds computation | Check distributed traces. If 80% of request time is hop-to-hop latency, you have too many hops |
| You haven't hit product-market fit yet | Optimizing architecture before validating the product | Honest conversation: do you have paying users who need this scale? |
If you recognize 3 or more, you likely have premature microservices.
The "services per engineer" ratio
A practical heuristic: count your services and divide by your team size.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.