The "Microservice Regret" Era: Why Teams Are Quietly Going Back to Monoliths
Why startups and engineering teams are quietly returning to modular monoliths after experiencing the hidden operational costs and complexity of premature microservices.
For a while, microservices felt like the obvious next step for any serious software product.
As soon as a startup started growing, the architecture conversation would eventually turn toward splitting the application into smaller services. Authentication became its own service. Payments got separated. Notifications moved somewhere else. Before long, a product that started as a relatively simple application could end up with a surprisingly large number of services running behind it.
And honestly, there were good reasons for doing this.
Microservices solve real problems. They can let teams deploy parts of a system independently, scale specific workloads without scaling everything else, and give larger engineering teams more ownership over different parts of a product.
For companies operating at significant scale, that separation can be incredibly useful.
The problem starts when the architecture arrives before the problem does.
A five-person startup with a few thousand users doesn't have the same architectural needs as a company with hundreds of engineers and millions of daily requests. Yet it's surprisingly easy to build the smaller product as if that future has already arrived.
That's where the microservice regret starts.
Microservices Aren't the Problem
It's worth making one thing clear: this isn't an argument against microservices.
They aren't bad. In the right situation, they're extremely useful.
The problem is adopting them simply because they feel like the “proper” way to build a modern application, rather than because the product actually needs them.
Imagine a small application with authentication, payments, user profiles, and notifications.
You could turn each of those into its own service. On an architecture diagram, that might look clean and well organized.
But now every service needs a deployment process, monitoring, logging, configuration, authentication, error handling, and documentation.
Something that could have been a function call inside the same application is now a network request.
And network requests can fail.
A service can time out. A database can become temporarily unavailable. A request can get retried. One service can deploy a change before another service is ready for it. Suddenly, you're dealing with distributed-systems problems that simply didn't exist when everything lived inside one application.
The product hasn't necessarily become more complicated.
The architecture has.
The Hidden Cost of Being “Scalable”
When people talk about scalability, they usually mean traffic.
Can the system handle ten times more users? Can it process more requests? Can a particular part of the application be scaled independently?
Those are important questions, but there's another kind of scalability that's easy to overlook: how well the team itself can understand and change the system.
A system that can handle millions of requests but takes three days to understand before making a small change isn't exactly easy to work with.
That's one reason the modular monolith has started looking attractive again.
Instead of immediately splitting everything into independent services, a team can keep one application while still maintaining clear boundaries inside it. Authentication, billing, users, notifications, and other domains can have their own modules and responsibilities without requiring a separate service and infrastructure for each one.
You get structure without immediately paying the full operational cost of distributed systems.
And if one of those modules eventually becomes large enough to justify becoming a separate service, you can extract it later.
That flexibility matters.
Design store-ready screenshots in minutes — free, no watermarks.
Launch Shots is free forever. Every template(110+), 220+ device frames plus 3D mockups, AI localization to 84 store languages, and direct upload to App Store Connect and Google Play Console. No credit card needed.
Start creating free →You Don't Have to Predict the Future
One of the easiest ways to over-engineer a startup is to build around problems that haven't happened yet.
“We might have millions of users.”
“We might have multiple engineering teams.”
“We might need to scale this component independently.”
“We might switch databases later.”
All of these things are possible.
But possible doesn't mean probable, and probable doesn't mean you need to solve it today.
A startup's architecture should mostly respond to the problems the team is actually experiencing, rather than an imaginary version of the company five years from now.
If the system eventually needs to be split, you can split it when the reason becomes clear.
Until then, keeping things together can make development faster, debugging easier, and the entire system easier to understand.
That doesn't mean ignoring future growth.
It means accepting that you probably don't know exactly what that growth will look like yet.
And that's okay.
Every Service Creates Another Responsibility
It's easy to look at a microservice architecture and see a collection of small, nicely separated pieces.
What gets overlooked is that every one of those pieces comes with an operational cost.
A service needs deployment. It needs monitoring. It needs logs. It needs alerts. It needs configuration. Someone needs to understand what happens when it goes down. Someone needs to investigate it when its latency suddenly increases.
Now imagine doing that for ten services.
Then twenty.
Then fifty.
For a large organization, that overhead can absolutely be worth it. The independence between teams and systems can provide enough value to justify all that complexity.
For a small team, though, it can become a distraction.
Instead of spending time improving the product, developers can end up spending a significant amount of time maintaining the machinery required to keep the architecture running.
And that's one of the less obvious costs of microservices.
The code itself might not be particularly difficult.
It's everything around the code that starts adding up.
The Modular Monolith Deserves More Respect
The modular monolith sits somewhere between the two extremes.
It's still one application, but it doesn't have to be one giant, tangled codebase where everything can access everything else.
The application can be divided into logical modules with clear responsibilities and boundaries.
That means developers can think in terms of separate domains without introducing a network boundary between every domain.
Development can also remain relatively simple. Running the application locally is easier. Debugging is generally more straightforward. Transactions can be easier to manage. Deployment doesn't require coordinating a collection of independent services.
And there's less infrastructure to maintain.
Most importantly, starting with a monolith doesn't mean you're stuck with it forever.
A well-structured monolith can actually make a future move toward microservices easier because the boundaries already exist.
If the billing module eventually becomes the part of the system that genuinely needs independent scaling or deployment, you can extract it.
You don't have to extract everything just because one part needs to change.
That's the real advantage of designing good boundaries.
You're giving yourself options.
So When Do Microservices Actually Make Sense?
There isn't a magic number of users where a company should suddenly switch from a monolith to microservices.
Traffic alone isn't enough to make that decision.
Microservices become much more compelling when different parts of the system have genuinely different scaling requirements, when teams need to deploy independently, when domain boundaries are well understood, or when certain components require completely different technologies or infrastructure.
Team structure matters too.
An organization with dozens of engineering teams has very different coordination problems from a startup with four developers.
The architecture should respond to those realities.
If you're splitting a service because one component is causing a real scaling problem, that's a reason.
If you're separating a domain because multiple teams need to work on it independently, that's a reason.
If you're doing it because “we might need it someday,” it might be worth waiting.
The best architectural decision is often the one that solves today's problem without creating five new ones.
The Most Sophisticated Architecture Might Be the Boring One
Software engineering has a tendency to reward complexity.
A diagram filled with services, queues, databases, event buses, and infrastructure components can look impressive.
But users don't see any of that.
They see whether the product loads quickly. They see whether their data is safe. They see whether a feature works when they need it. They see whether the product feels reliable.
That's what the architecture is ultimately there to support.
Sometimes that means microservices.
Sometimes it means a modular monolith.
Sometimes it means something somewhere in between.
The mistake is assuming that a more complicated architecture automatically means a more mature engineering team.
Good architecture isn't about how many technologies you can fit into a system.
It's about building something that's easy enough to understand, reliable enough to operate, and flexible enough to change when the product changes.
Maybe that's why some teams are moving back toward monoliths.
They're not necessarily abandoning scalability.
They're realizing that simplicity is a form of scalability too — especially when the thing that needs to scale isn't just the infrastructure, but the team building the product.
