Monolith vs. Microservices: The Honest Answer
- ShiftQuality Contributor
- Sep 14, 2025
- 5 min read
The industry narrative goes like this: monoliths are old, microservices are modern, and if you're not breaking your application into independently deployable services, you're doing it wrong.
This narrative was written by companies with thousands of engineers, millions of users, and infrastructure teams larger than your entire company. It was correct for their context. Applied to your context — a team of 3-15 people building a product — it's often exactly wrong.
The honest answer isn't "monoliths are better" or "microservices are better." It's "the right architecture depends on your team size, your scale, and where you are in the product lifecycle." And for most teams at most stages, the monolith wins.
What Each Actually Means
Monolith
One deployable unit. All your code — API endpoints, business logic, data access, background jobs — lives in one application, one repository, one deployment pipeline.
This doesn't mean spaghetti code. A well-structured monolith has clear modules, separation of concerns, and defined boundaries between features. The code is organized; it's just deployed together.
Microservices
Multiple independently deployable services, each responsible for a specific business capability. An order service, a user service, a payment service — each with its own database, its own deployment, and its own team.
Services communicate over the network (HTTP, gRPC, message queues). Each service can be developed, deployed, and scaled independently.
The Case for Starting With a Monolith
You Ship Faster
One repository. One deployment. One test suite. One set of infrastructure. When you need to change something that touches users and orders, you make the change, run the tests, and deploy. In microservices, the same change might require coordinating changes across three services, three deployments, and three test suites.
For a small team building a product that's still finding its market, speed of iteration is the most valuable property of your architecture. Every hour spent on inter-service communication is an hour not spent on the product.
You Avoid Distributed Systems Complexity
Microservices turn every function call into a network call. Network calls fail. They time out. They have latency. They require serialization and deserialization. They need retry logic, circuit breakers, and fallback strategies.
A monolith function call: orderService.createOrder(data) — takes microseconds, never fails due to network issues, and doesn't need a retry strategy.
A microservice equivalent: HTTP POST to https://order-service/api/orders — can fail with network errors, timeout after 30 seconds, return stale data from a cache, lose the request in a queue, or succeed without the caller knowing. Now multiply this by every inter-service call in every request path.
Distributed systems are genuinely hard. The complexity isn't optional — it's inherent. If your team doesn't have experience operating distributed systems, microservices will make your product less reliable, not more.
You Can Refactor Freely
In a monolith, moving code between modules is a refactoring operation — change the imports, move the files, run the tests. In microservices, moving functionality between services is a migration — new API contracts, data migration, deployment coordination, backward compatibility.
When your product is evolving rapidly (which it should be in the early stages), the ability to reorganize code quickly is more valuable than the ability to deploy services independently.
Your Boundaries Aren't Clear Yet
Microservices require clear boundaries between services. If you draw the boundaries wrong — and you will, because you don't understand the domain well enough yet — you'll end up with chatty services that make 20 inter-service calls per request, or services that need each other's data so often that they're a distributed monolith with network overhead.
A monolith lets you discover the right boundaries through experience. When you eventually understand which parts of the system change independently and which are tightly coupled, you can extract services along those natural boundaries. This is much easier than redrawing boundaries between existing services.
When Microservices Make Sense
Multiple Teams Need to Ship Independently
When you have 5+ teams and they're stepping on each other — merge conflicts, deployment queues, waiting for another team's code review — microservices solve a coordination problem. Each team owns their services and deploys on their own schedule.
This is the primary reason microservices exist: organizational scaling. If you have one team, you don't have this problem.
Parts of the System Have Different Scaling Needs
Your search feature gets 100x the traffic of your admin panel. In a monolith, you scale the entire application to handle the search traffic. In microservices, you scale only the search service.
This matters at significant scale. At moderate scale, scaling the whole monolith is usually cheaper and simpler than the overhead of managing separate services.
Parts of the System Need Different Technologies
Your main application is in Python, but the real-time processing component would benefit from Go's concurrency model, and the ML pipeline needs specific Python libraries that conflict with your web framework.
Microservices let each component use the best tool for its job. This advantage is real but often overstated — most applications can be built entirely in one language.
You've Outgrown the Monolith
The monolith deployment takes 45 minutes. A change in the payment module breaks the notification system. The codebase is so large that no one understands all of it, and changes have unexpected consequences.
These are symptoms that a monolith has outgrown its architecture. Extracting the most problematic modules into services — incrementally, one at a time — is the pragmatic response.
The Middle Path: Modular Monolith
A modular monolith gives you most of the organizational benefits of microservices without the distributed systems complexity. The code is structured into independent modules with clear interfaces, but it deploys as one unit.
src/
modules/
orders/ # own models, services, controllers
users/ # own models, services, controllers
payments/ # own models, services, controllers
shared/ # cross-cutting concerns
Each module has its own internal structure and communicates with other modules through defined interfaces — not direct database access. This structure makes future extraction into services straightforward: the interface already exists, you just put a network boundary on it.
The modular monolith is the best starting architecture for most new products. It gives you monolith deployment simplicity with microservice-like organizational boundaries.
The Decision Framework
| Factor | Monolith | Microservices | |--------|----------|---------------| | Team size | 1-15 people | 15+ people, multiple teams | | Product stage | Early, iterating | Mature, stable boundaries | | Scale | Moderate | Extreme or highly variable | | Domain understanding | Still learning | Well understood | | Ops capability | Basic | Dedicated platform/infra team | | Deployment frequency | Weekly-daily | Multiple times daily per team |
If you're on the left side of most rows, start with a monolith. Extract services when specific problems demand it. This isn't a compromise — it's the approach that companies like Shopify, Stack Overflow, and Basecamp have used successfully at significant scale.
Key Takeaway
Start with a monolith. It's faster to build, simpler to operate, and easier to refactor. Extract services when you have multiple teams that need to deploy independently, components with radically different scaling needs, or a monolith that's genuinely too large to manage. The modular monolith gives you the best of both worlds for most teams at most stages. Don't adopt microservices because the internet says they're modern — adopt them when the specific problems they solve are problems you actually have.



Comments