How do you decide between a modular monolith and microservices?
The choice turns on team topology and independent deployability, whether domain boundaries are stable, data ownership and distributed-transaction cost, and real operational maturity. Default to a modular monolith with enforced internal boundaries and extract services only on evidence.
What the interviewer is scoring
- Whether you reject the premise that one style is inherently more modern and instead name the forces that decide it
- Whether you connect service boundaries to team ownership and deployment independence rather than to code size
- Whether you can articulate what a network boundary costs you in consistency, debugging and latency
- Whether you name the distributed monolith as the specific failure mode of premature extraction
- Whether you can state the evidence that would justify extracting a particular module later
Answer
Reject the framing first
Microservices are not a newer version of a monolith, and a modular monolith is not a microservice architecture that has not finished loading. They are two different answers to the question of where you put your boundaries: a modular monolith puts them at compile time inside one deployable, and microservices put them at runtime across a network. Modularity is the goal in both cases. The only thing genuinely under discussion is whether each module needs its own deployment lifecycle, its own runtime, and its own database, and whether you are willing to pay a network and operational bill to get that.
Saying this out loud early is worth more than any subsequent detail, because most weak answers treat the decision as a maturity ladder and then justify microservices with properties (clean boundaries, testability, domain isolation) that a monolith gets for free.
The forces that actually decide it
Team topology and independent deployability. The strongest reason to split a service out is that a team needs to ship on its own cadence without coordinating a release with anyone else. If four teams work on one deployable and every release is a joint negotiation, you have a real, measurable problem that service boundaries solve. If you have twelve engineers in one team, you do not have that problem, and services will only add ceremony. The useful test is not how large the codebase is but how many independent decision-makers are contending for the same release train.
Whether the boundaries are yet known. Getting boundaries wrong inside a monolith costs a refactor: move packages, fix imports, run the tests. Getting them wrong across services costs a distributed migration with dual writes, backfills, and a versioned contract. Early in a product's life, the domain model is still being discovered, so the cheap-to-move option is objectively better. Once a subdomain has been stable for a year, with a clear language of its own and few reasons to change alongside its neighbours, extracting it is a low-risk move.
Data ownership and the cost of distributed transactions. This is where extraction usually hurts most. A monolith can enforce an invariant across two aggregates with one database transaction. The moment those aggregates live in separate services with separate stores, that invariant becomes a saga with compensating actions, or an outbox and an eventual-consistency window the business must accept. If you cannot say which service owns each piece of data, and cannot describe what a partial failure looks like to a user, the split is not designed yet. Consistency requirements, not code structure, are what make some boundaries genuinely unsplittable.
The operational maturity you actually have, not the one you plan to have. Microservices convert design problems into operations problems, so they presuppose distributed tracing, centralised logging with correlation IDs, per-service CI/CD, service-level objectives, and someone on call who can reason about cascading failure. If a request currently cannot be followed end to end, splitting it across six hops makes every incident longer. Judge this on what exists today, because the platform investment always lands after the services do.
Why the modular monolith is the sane default
The default that survives challenge is one deployable with hard internal boundaries: modules that own their data, communicate through explicit interfaces or in-process domain events, and never reach into each other's internals. Crucially, those boundaries must be machine-enforced, because a convention that only lives in a review comment is gone within two quarters. Java's module system, a build tool's dependency rules, or an architecture test all work.
// Enforced at build time: nothing outside billing may touch its internals.
// A convention nobody can violate is the difference between a modular
// monolith and a monolith that people describe as modular.
ArchRule rule = noClasses()
.that().resideOutsideOfPackage("..billing..")
.should().dependOnClassesThat().resideInAPackage("..billing.internal..");
The payoff is optionality. You keep single-process transactions, one deployment, one place to debug, and refactoring that a compiler verifies, while retaining the seams that make later extraction a matter of replacing an in-process call with a remote one. Extraction then becomes evidence-driven: a module has a different scaling profile, or a different compliance boundary, or a team that is demonstrably blocked on the shared release, or a failure domain you want isolated. Each of those is a reason you can point at in production data. "It will scale better" is not.
The trap: the distributed monolith
The failure mode that separates a strong answer from an adequate one is naming the distributed monolith explicitly. That is what you get when you split the deployables but not the coupling: services that must be released together because their contracts change together, that call each other synchronously in a chain so any one outage takes down the request, and that still share a database so no team can change a schema alone. You have then paid the entire cost of distribution — network failures, serialisation, eventual consistency, multiplied observability effort — and received none of the independence you bought it for. It is strictly worse than the monolith you started with, and it is the normal outcome of extracting along technical layers or by code size rather than along business capabilities and ownership.
A related trap is answering as though the decision were permanent. The honest framing is a sequencing question: which boundaries do I want in code now, and which do I want on the network later, given that moving in one direction is far cheaper than moving back.
Boundaries are the architecture; deployment topology is an implementation detail of them. Get the modules right in one process, and let production evidence — not fashion — tell you which ones have earned a network hop.
Likely follow-ups
- What signals in a running system would tell you a specific module is now worth extracting?
- How would you preserve a business invariant that used to be a single database transaction once it spans two services?
- How do you stop a modular monolith's internal boundaries from eroding over eighteen months?
- You have inherited forty services maintained by three teams. What do you do?
Related questions
- How do you decide where one service ends and the next begins, and when is a modular monolith the more honest answer?hardAlso on microservices and service-boundaries7 min
- A payment API times out and the client retries. How do you guarantee the customer is not charged twice?hardAlso on distributed-systems6 min
- Two teams own two services that talk to each other. How do you stop one breaking the other without running both together in a shared environment?mediumAlso on microservices4 min
- The clearing house has called far more initial margin than your own risk system predicted. How do you find out why before the deadline?hardSame kind of round: scenario6 min
- Your data is pinned per region, but login must be globally unique and admins want one global search. How do you build that?hardSame kind of round: design6 min
- A legal hold lands on Friday afternoon and your nightly deletion jobs run at two. What do you do, and what do you build afterwards?hardSame kind of round: scenario5 min
- The operations team says there is no rule for this, they just use judgement. How do you model that?hardSame kind of round: design6 min
- Fraud and AML say keep it for years, privacy says delete it. How do you build a system that satisfies both?hardSame kind of round: design5 min