Monolith vs Modular Monolith vs Microservices — picking the right shape for your stage
A practical breakdown of when a monolith is the right call, when to modularise it, and when microservices actually earn their operational cost — with the signals that tell you it's time to move.
- Architecture
- Microservices
- Backend
- Startups
The architecture debate that never dies. Every engineering team eventually has it — usually at the worst possible time, when the codebase is already painful and the deadline is already close. The answer isn't "microservices are modern" or "monoliths are simpler." The answer is the right shape for your current stage, with a clear path to the next one.
This is our opinionated take on when each architecture earns its keep, what the real costs are, and the signals that tell you it's time to move.
The three shapes, plainly
Monolith: one deployable unit. All modules — auth, billing, orders, notifications — live in the same process, share the same database, deploy together. Simple to build, simple to run, simple to debug. The shape that ships fastest.
Modular monolith: still one deployable unit, but the internal structure is enforced. Modules have explicit boundaries — they don't reach into each other's database tables, they communicate through defined interfaces, and the dependency graph is intentional. You get most of the operational simplicity of a monolith with most of the boundary clarity of microservices.
Microservices: multiple deployable units, each owning its own data, communicating over the network. Independent deployability, independent scaling, independent failure domains. Also: distributed systems complexity, network latency, eventual consistency, and an operational surface area that requires a real platform team to manage.
Why almost every successful product started as a monolith
Amazon, Netflix, Shopify, Basecamp — all started as monoliths. The ones that moved to microservices did so years in, under specific scaling pressures, with large engineering teams to absorb the operational cost.
The reason isn't nostalgia. It's that a monolith is the fastest way to learn what your product actually is. In the early stages, your domain model is wrong. Your service boundaries will be wrong. The thing you thought was one service turns out to be three; the thing you thought was three turns out to be one. If you've already split those into separate deployable services, refactoring the boundary is a distributed systems migration. If they're modules in a monolith, it's a refactor.
The cost of getting boundaries wrong in a monolith is a bad afternoon. The cost of getting them wrong in microservices is a multi-week migration with coordination across teams.
The modular monolith: the shape most teams skip and shouldn't
The modular monolith is the most underrated architecture in the industry. It's what you should build when you've outgrown a naive monolith but haven't yet hit the scaling pressures that justify microservices.
What "modular" actually means in practice:
- Each module owns its own database schema (or at minimum, its own set of tables). No cross-module joins.
- Modules communicate through in-process interfaces, not direct table access.
OrderService.place(...)notSELECT * FROM orders. - The dependency graph is enforced — a linter or architecture test fails if
billingimports frominventorydirectly. - Each module could, in theory, be extracted into a separate service. The boundary is already there; you're just not paying the network tax yet.
This last point is the key one. A well-structured modular monolith is a microservices architecture waiting to be deployed. When you do need to extract a service — because one module needs to scale independently, or because a separate team needs to own it — the extraction is a deployment change, not a design change.
When microservices actually earn their cost
Microservices are not a best practice. They're a solution to specific problems that most teams don't have yet. The problems they solve:
Independent scaling. If your image-processing pipeline needs 50× more compute than your auth service, and they're in the same process, you're scaling both together. Separate services let you scale each independently. This matters at real scale; it doesn't matter at 10,000 users.
Independent deployability. If you have 8 teams shipping to the same codebase, deploy coordination becomes a bottleneck. Separate services let each team deploy on their own cadence. This matters with 8 teams; it doesn't matter with 2.
Fault isolation. A memory leak in the recommendation engine shouldn't take down checkout. Separate processes give you that isolation. This matters when uptime is a contractual obligation; it's overkill when you're still finding product-market fit.
Technology heterogeneity. Sometimes the right tool for one job is Python (ML inference) and the right tool for another is Go (high-throughput API). Separate services let you pick. This matters when the performance or ecosystem difference is real; it doesn't justify the complexity otherwise.
If none of those problems are real for you today, microservices are adding cost without adding value.
The signals that tell you it's time to move
From monolith to modular monolith:
- You have more than one team touching the codebase and stepping on each other.
- A change in one area keeps breaking something unrelated.
- You can't reason about what a module depends on without reading the whole codebase.
- Deploy confidence is low because "everything is connected to everything."
From modular monolith to microservices:
- One module genuinely needs to scale independently and the cost of co-scaling is real.
- You have separate teams that need separate deploy cadences and the coordination overhead is measurable.
- A module has a fundamentally different operational profile (stateful, GPU-bound, event-driven) that doesn't fit the monolith's runtime.
- You've extracted the module boundary cleanly in the modular monolith and the extraction is now a deployment change, not a design change.
What is not a signal to move to microservices: "it's what modern companies do," "we want to be cloud-native," "our monolith is slow" (that's a performance problem, not an architecture problem).
The migration path that actually works
The pattern we'd recommend for any team that's starting fresh or refactoring:
- Start with a monolith. Ship fast, learn the domain, don't over-engineer.
- Enforce module boundaries early. Even in a monolith, treat modules as if they were services — no cross-module table access, explicit interfaces. This costs almost nothing and pays off enormously later.
- Move to a modular monolith when the team grows. Formalise the boundaries, add architecture tests, make the dependency graph explicit.
- Extract services only when a specific scaling or team-ownership problem forces it. Extract one at a time, starting with the module that has the clearest boundary and the most independent scaling need.
The teams that end up with unmaintainable microservices architectures almost always skipped step 2. They split services before they understood the domain, got the boundaries wrong, and then couldn't refactor because the boundaries were now network contracts.
What we'd actually build today
For a new product with a small team: a modular monolith from day one. Not a naive monolith (no module discipline), not microservices (too much operational overhead). A single deployable unit with enforced internal boundaries, a clean dependency graph, and a database schema that's already partitioned by module.
For a product that's hit real scaling pressure on a specific module: extract that module as a service, leave everything else in the monolith. One service, not twelve.
For a product with multiple large teams and genuinely independent scaling needs: microservices, but with a platform team, a service mesh, distributed tracing, and the operational maturity to support it. Not before.
TL;DR
Start with a monolith. Enforce module boundaries from the beginning. Move to a modular monolith when the team grows. Extract services only when a specific problem forces it — not because it's fashionable.
The architecture that ships your product is better than the architecture that would have been perfect if you'd had six more months to design it.
If you're scoping a new product or refactoring an existing one and want a second opinion on the right shape for your stage, let's talk.
