Home › High-Level Design › Monolith vs Microservices
M

Monolith vs Microservices

This is the first architecture decision an interviewer watches you make, and the one most candidates get wrong by reflex. The second someone hears "design a system," they start drawing fifteen boxes with arrows everywhere. That instinct loses points. The strong answer is almost always "start with one service and split when you have a reason to." I have sat on both sides of this question, and the gap between a weak and a strong answer here is the gap between an inclined and a strong-hire signal on the architecture rubric.

High-Level DesignBeginner11 min read

If you remember five things

The whole position in one glance. Everything below defends these five calls.

  1. Default to the monolith. One deployable, one database, ACID transactions. You split later for a reason, not because many boxes look impressive.
  2. Own-database-per-service is the real line. Not the count of services. Five services sharing one DB is a monolith in a costume, all pain, no payoff.
  3. Microservices trade one problem for another. You swap the pain of a big shared codebase for the pain of a distributed system. The network is the new failure surface, and it lies.
  4. The two winning triggers are people and scaling. Split when teams block each other on deploys, or when one piece has genuinely different scaling or hardware needs.
  5. Consistency is where candidates get burned. The moment checkout and inventory are two services, you lose the shared transaction and owe sagas, idempotency keys, and an outbox.

1The core idea#

Two shapes for the same application. Know exactly what each one is before you argue for either.

A monolith is one deployable unit.

  • All your code (checkout, user accounts, product catalog, the email sender) lives in a single codebase.
  • It ships as a single process, or a set of identical copies of that same process behind a load balancer.
  • When you deploy, you deploy the whole thing.

Microservices split that same application into many small, independently deployable services.

  • Checkout is its own service with its own database. Users is another. Catalog is another.
  • Each team owns its service end to end.
  • Services talk over the network, usually HTTP or a message queue.

The mental model I use:

  • A monolith is one big house where everyone shares the kitchen, the plumbing, and the front door.
  • Microservices are a street of separate houses, each with its own plumbing, that have to phone each other to coordinate dinner.
The thing most beginners miss

Microservices are not "the advanced version" of a monolith. They are a different set of problems. You trade the pain of a large shared codebase for the pain of a distributed system. Both are real. One of them involves the network, and the network is a liar.

2How it works#

Same checkout flow, two completely different failure stories. This is the heart of the trade.

In a monolith, a function calling another function is just a function call.

  • Checkout calls reserveInventory() directly, same process, same memory.
  • It is fast: roughly a 5ns in-process call versus a 1ms to 5ms network round trip, about a million times faster.
  • It either works or throws, no middle state.
  • You wrap the whole thing in one database transaction. Payment succeeds but inventory fails? Roll back both. Clean.
flowchart LR A[Client] --> B[Load Balancer] B --> C[Monolith App] C --> D[(Database)] C --> E[(Cache)]
The monolith at a glance. One process, one database, one transaction boundary. Everything the request touches lives inside that single box, so there is no network between the pieces that matter.

Scaling a monolith is "run more copies behind the load balancer."

  • All copies hit the same database.
  • Simple to reason about, right up until the database becomes the bottleneck.

In microservices, that same checkout flow turns into network calls.

  • Checkout service makes an HTTP request to inventory service.
  • Inventory has its own database that checkout cannot touch directly.
flowchart LR A[Client] --> B[API Gateway] B --> C[Checkout Service] B --> F[Catalog Service] C --> G[Inventory Service] C --> D[(Checkout DB)] G --> H[(Inventory DB)] F --> I[(Catalog DB)]
The microservices split. Notice each service owns its own database and nobody reaches across. That boundary, not the number of boxes, is what actually makes this microservices.

Two things that diagram is telling you.

  • Each service has its own database. This is the rule that defines microservices. If five services share one database, you have a monolith wearing a costume: all the pain, none of the benefit.
  • That call from checkout to inventory can now fail in ways a function call never could. It can time out. It can succeed but the response gets lost on the way back, so checkout thinks it failed, retries, and now you double-decrement stock. Inventory can be mid-deploy.

So you pay the distributed-systems tax.

  • Add retries, timeouts, circuit breakers.
  • Lose your single database transaction.
  • Keeping checkout and inventory consistent is now a distributed problem you solve with sagas or outbox patterns, a whole topic on its own.

3Trade-offs#

Here is the decision laid out honestly. Nothing here is free.

Dimension Monolith Microservices
Initial speed to build Fast. One repo, one deploy, run it locally in one command Slow. Service boundaries, networking, CI per service
Local development Clone and run Hard. You may need 6 services running to test one flow
Deploy One artifact, all or nothing Each service ships on its own schedule
Blast radius of a bug A bad deploy can take down everything A bad deploy ideally takes down one service
Scaling Scale the whole app even if only search is hot Scale only the hot service
Data consistency Easy. One DB, ACID transactions Hard. Distributed transactions, eventual consistency
Debugging a request Read one stack trace Trace a request across 8 services and 3 queues
Team autonomy Teams step on each other in one codebase Each team owns and ships independently
Operational cost Low. One thing to monitor High. Service mesh, tracing, per-service dashboards

The honest one-line summary:

  • A monolith optimizes for a small team shipping fast.
  • Microservices optimize for many teams shipping independently at large scale, and you pay a steep operational tax for that.

A worked example with real numbers#

This is the one place you win points with arithmetic instead of adjectives. So do the arithmetic.

Set the scene.

  • Your app is a monolith serving 10,000 requests per second.
  • One copy (a single pod) handles about 1,000 req/s, so you run 10 copies with a little headroom.
  • The traffic mix matters: 1,000 of those req/s hit a CPU-hungry image-processing endpoint, and the other 9,000 are light CRUD (product pages, cart reads) that cost almost nothing.

The monolith is forced to overscale.

  • You cannot scale the image endpoint on its own, because it is baked into the same process as everything else.
  • The CPU-heavy image work sets the pace, so to keep p99 healthy you end up running 20 copies.
  • Nineteen of them are mostly carrying image capacity for traffic that is not image processing.
  • At, say, $100 per pod per month, that is $2,000/month, and most of it is wasted.

Split the image work and the math flips.

  • Image service needs enough for 1,000 req/s of heavy work: call it 15 copies.
  • Everything else needs enough for 9,000 req/s of light work: 3 copies is plenty.
  • That is 18 pods total, but now each group scales on its own signal.
  • Image-heavy campaign spikes to 2,000 req/s? Add 15 image pods, touch nothing else. In the monolith you would have doubled all 20 copies to 40 to absorb the same spike.
10%
of traffic drove the whole pod countIn the monolith, the CPU-heavy image endpoint was a tenth of requests but set the scaling pace for everything. Targeted scaling is the single most concrete win you can point to, and it is a real one.

Stay honest about the cost side.

  • You now run two deploy pipelines, two sets of dashboards, and a network hop between them that did not exist before.

The consistency trade-off#

This is where candidates get burned, so name it before the interviewer makes you.

  • In a monolith, "charge the card and decrement stock" is one transaction.
  • In microservices those are two services, two databases, two network calls, no shared transaction.
  • Charge succeeds and the stock update fails? You now owe the customer a refund, or you oversold the item.
  • Solving this (sagas, idempotency keys, an outbox table) is genuinely harder than anything in a monolith.
From the interviewer's chair

When a candidate volunteers this consistency trade-off before I ask, they jump a tier in my notes. It tells me they have actually felt the moment a single transaction turns into a saga, not just read the word on a slide. Say it out loud early.

4Where it is used#

History is blunt here: nearly every successful company you can name started as a monolith.

  • They built a single large application.
  • They grew to hundreds of engineers who kept colliding in that one codebase.
  • Only then did they peel off services.
  • The order matters: they split because they hit real, measurable pain, not because microservices were fashionable.

Reach for a monolith when:

  • You are a startup or small team, say under 20 engineers. You do not have the headcount to run a dozen services. Rough rule: if you cannot staff at least one owner per service, you have too many services.
  • Your product is still finding its shape. Boundaries you draw today will be wrong in three months. Moving a boundary inside one codebase is a refactor. Moving it across services is a migration project.
  • You want to ship this quarter.

Reach for microservices when:

  • You have many teams and they are blocking each other on deploys. This is the real trigger. When "we can't ship because another team's change broke the build" happens weekly, independent deploys start paying for themselves.
  • Parts of the system have genuinely different scaling or hardware needs. A GPU-heavy ML model and a lightweight CRUD API do not belong in the same deployable, the way the image service above did not belong with the product pages.
  • Different pieces have different reliability requirements. You want payments isolated so a bug in the recommendations feature cannot take down checkout.
Pause before you read on: a 10-engineer startup wants to launch in a quarter but "might scale to millions later." Monolith or microservices, and why?
Reveal the answer
Monolith, almost certainly a modular one. "Might scale later" is not a present trigger, and at 10 engineers you cannot staff owners for a dozen services. You would spend the quarter on YAML and service discovery instead of the product, and lock in boundaries before you understand the domain. Build the monolith with clean internal seams, hit real pain, then split the specific piece that hurts. Reaching for the modular monolith here reads as maturity, not indecision.

A middle path worth naming: the modular monolith.

  • One deployable, but with strict internal module boundaries, so catalog code cannot reach into checkout's tables.
  • You get monolith simplicity now and clean seams to split later if you ever need to.
  • For most teams this is the right answer for a long time, and saying so reads as maturity, not indecision.

5Common pitfalls and gotchas#

The failure modes that tank real systems, and the ones interviewers love to bait you into.

The distributed monolith.

  • You split into services but they share a database, or every request fans out to five synchronous calls to other services.
  • A change to one service now forces changes to others, you still cannot deploy independently, and you added network latency and failure for nothing.
  • This is the worst of both worlds, and it is extremely common.

Splitting too early.

  • A three-person team with eight microservices spends its time on YAML, service discovery, and tracing instead of the product.
  • You cannot find good service boundaries before you understand the domain, and you understand the domain by building it.
  • Premature splitting locks in the wrong boundaries in the most expensive possible way.

Forgetting the network is unreliable.

  • In a monolith, a call to another module does not time out. Across services it can, and will, at the worst moment.
  • Every synchronous call needs a timeout and a plan for failure.
  • A missing timeout is how one slow service cascades into a full outage: callers pile up waiting, run out of threads, and go down too.

Chatty communication.

  • Rendering one page triggers 30 service-to-service calls, each a network round trip.
  • Thirty calls at 5ms each is 150ms of pure network waiting before any real work, and that is the best case where every call succeeds first try.
  • Monoliths do not have this problem, because those were function calls at a few nanoseconds each.

Shared data models.

  • Every service imports the same User object, so a change to it forces every service to redeploy in lockstep.
  • You have recreated tight coupling over the network, which is strictly worse than tight coupling in a single process.
Red flag that fails you here

Opening "design a system" by drawing a user service, an order service, a payment service, and a notification service before anyone has agreed on a single requirement. That pattern-matches buzzwords, not engineering, and it is the fastest way to tell me you have never operated a distributed system under load. Start with one service and earn each split.

6How it shows up in interviews#

Interviewers probe two things: whether you reach for complexity by default, and whether you understand what distribution actually costs.

The opening move. When asked to design something, do not open with microservices.

  • Weak: "I'll use a microservices architecture. We'll have a user service, an order service, a payment service, a notification service..." (drawing boxes before anyone agreed on a requirement). This says you pattern-match to buzzwords.
  • Strong: "I'll start with a single service and a single database, get the core checkout flow working, and call out where I'd split later and why. The first thing I'd split out is payments, once it needs to deploy on its own cadence." That one sentence says you have shipped real systems.

The "why split" question. Expect "when would you break this into services?"

  • Weak: "Microservices scale better and are more modern." This is fashion, not engineering.
  • Strong, naming a specific trigger: "When the search workload needs very different scaling from the rest, like my image service example where 10% of traffic drove the pod count, or when the payments team needs to deploy without coordinating with everyone else." Tie the split to concrete pain, with a number if you can.

The consistency trap. The classic follow-up: "You split checkout and inventory into separate services. How do you keep them consistent?"

  • Weak: "They'll both write to the database and it'll be fine," or a vague wave at "eventual consistency" with no mechanism.
  • Strong: "I lose the single database transaction the moment these are two services. I'd run a saga: charge the card, then publish an event, and the inventory service consumes it. I make every step idempotent with an idempotency key so a retry does not double-decrement, and I use an outbox table so the event and the local write commit together." Even naming the problem correctly beats pretending it does not exist.

The failure question. "Service B is down. What happens to A?"

  • Weak: "A will throw an error," full stop.
  • Strong: "A has a timeout on that call, say 500ms, retries once with backoff, and a circuit breaker that stops hammering B after a few failures so A does not exhaust its thread pool waiting. If B is non-critical, A degrades gracefully and serves a cached or default response." In a monolith this question barely exists. In microservices it is the whole game.

Microservices are not better. They are a bill you pay once the pain is real.

Follow-ups the interviewer will ask (and your answers)
"When exactly would you split your monolith into services?"
Two triggers. People: teams are blocking each other on deploys weekly. Scaling: one piece has genuinely different scaling or hardware needs, like a GPU model next to a CRUD API. I split that specific piece, name the pain (ideally with a number), and leave the rest as one deployable. I do not split on a hunch that we "might scale later."
"You split checkout and inventory. How do you keep them consistent without a shared transaction?"
A saga: charge the card, publish an event, inventory consumes it. Every step is idempotent via an idempotency key so a retry cannot double-decrement stock, and I use an outbox table so the local write and the published event commit together atomically. If a step fails, the saga runs compensating actions (refund, release the hold).
"Service B is down. What happens to A?"
A has a timeout on the call (say 500ms), retries once with backoff, and a circuit breaker that stops hammering B after repeated failures so A does not exhaust its thread pool waiting. If B is non-critical, A degrades gracefully with a cached or default response. The missing-timeout version of this is how one slow service cascades into a full outage.
"Five of your services share one database. Is that microservices?"
No, that is a distributed monolith. A schema change forces several services to redeploy in lockstep, so you still cannot deploy independently, and you have added network latency and failure for zero benefit. Own-database-per-service is the line that actually defines microservices, not the number of boxes.
"What is the modular monolith and when do you recommend it?"
One deployable with strict internal module boundaries, so catalog code cannot reach into checkout's tables. You keep ACID transactions and single-process simplicity now, while building clean seams to split later. For most teams under 20 engineers it is the right answer for a long time, and it lets you split the one piece that hurts without rewriting everything.

What you can now explain in the room

  • Why "start with one service and split for a reason" beats drawing fifteen boxes up front.
  • Why own-database-per-service, not the service count, is what actually defines microservices.
  • How a 10%-of-traffic CPU endpoint forces a monolith to overscale, and how splitting flips the math.
  • Why a single transaction becomes a saga with idempotency keys and an outbox the moment you split.
  • How one down service cascades into a full outage without timeouts and circuit breakers.
  • Why the modular monolith is often the mature answer, and the two real triggers (people and scaling) for going further.

Want to see this play out end to end? Try the System Design Question walkthrough on designing an e-commerce checkout, where the monolith-to-services decision drives the whole design.

Get a prep plan built around your Monolith vs Microservices interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Monolith vs Microservices prep plan is on the way.