Skip to main content
PProfileMatch

Monolith vs Microservices: An Honest Decision Guide

Backend DevelopmentLesson 1 of 45 min read

What you'll learn

  • Define monolith, modular monolith and microservices by deployable unit, not by size
  • Quantify what a network hop costs in availability using simple arithmetic
  • List the genuine benefits of services and the organisational conditions they need
  • Run a pre-split checklist and decide when staying monolithic is the right answer

On this page

  1. Three shapes, defined by what you deploy
  2. What the network takes from you
  3. What services genuinely buy you
  4. A checklist to run before you split
  5. Common mistakes and the symptoms they cause
  6. How this shows up in real projects and interviews

A nine-person team runs a grocery-delivery startup on a single web application. The CTO returns from a conference and announces that the platform will be split into microservices by quarter end. Six months later the team owns fourteen repositories, a flaky staging environment that nobody can fully run on a laptop, and a feature that used to be one pull request is now four. Was the split wrong? Not necessarily. It was never examined. This lesson gives you the tools to examine it.

Three shapes, defined by what you deploy

Forget lines of code. The useful definition is how many things you release and run separately.

  • Monolith: one deployable unit. Everything (catalogue, cart, payments, delivery slots) ships together and usually shares one database. A monolith can be well structured or a tangle; "monolith" describes deployment, not quality.
  • Modular monolith: still one deployable unit, but with enforced internal boundaries. Modules expose a small public interface, never reach into each other's tables, and could be extracted later with moderate effort.
  • Microservices: several independently deployable services, each owning its own data, communicating over a network. Independence is the point: a service can be changed, released and scaled without coordinating a release of the others.

What the network takes from you

Inside one process, calling cartService.total(cartId) either returns or throws, takes microseconds and cannot be half-delivered. Across a network the same call can time out after the other side already acted, return slowly, or fail because a router somewhere restarted. You also lose transactions that span the call (the topic of the last lesson). The cheapest way to feel this is arithmetic: if a request must pass through several services in a row, their availabilities multiply.

chain_availability.py
# Availability of a request that must touch every service in a chain.
per_service = 0.999  # 99.9% each

for hops in (1, 3, 6, 10):
    chain = per_service ** hops
    downtime_min = (1 - chain) * 30 * 24 * 60  # minutes in a 30-day month
    print(f"{hops:>2} services in the call path -> {chain * 100:.2f}% available, "
          f"about {downtime_min:.0f} min/month unavailable")

Output

 1 services in the call path -> 99.90% available, about 43 min/month unavailable
 3 services in the call path -> 99.70% available, about 129 min/month unavailable
 6 services in the call path -> 99.40% available, about 259 min/month unavailable
10 services in the call path -> 99.00% available, about 430 min/month unavailable

The model is simplified: it assumes failures are independent and that every service is required for the request to succeed. Real systems soften this with timeouts, fallbacks and caching, and they also make it worse with retries and shared dependencies. But the direction is reliable. Every synchronous hop you add is a place for the request to fail, and you must design for it on purpose.

What services genuinely buy you

There are real benefits, and each one depends on something you have to bring yourself.

BenefitWhat it requires from youIf you lack it
Independent deploysBackward-compatible APIs, automated tests, a working pipelineLock-step releases, which is a monolith with extra steps
Independent scalingA component with a truly different load profileMore infrastructure for no measurable gain
Team autonomySeveral teams that are blocked by one shared codebaseOne team juggling many repositories
Fault isolationTimeouts, bulkheads and graceful degradationOne slow service takes every caller down with it
Technology choicePeople willing to operate each stackPager duty for five runtimes

Notice that most rows are about people and process more than about code. That is why the guidance below keeps returning to team size and delivery maturity. If you want the capacity side of the story, scalability and load balancing shows that a monolith behind a load balancer scales further than many people assume.

A checklist to run before you split

Answer these honestly, in writing, with the team. Each "no" in the first group is a reason to wait; each "yes" in the second group is a reason to consider splitting something specific rather than everything.

pre-split-checklist.txt
READINESS (all should be YES before splitting)
[ ] Can we deploy the monolith to production on demand, safely, in minutes?
[ ] Do we have automated tests that catch breaking changes?
[ ] Can we trace one request across components (logs with a request id)?
[ ] Do we know our domain well enough to name the seams?
[ ] Is someone on call who can debug a distributed failure?

PRESSURE (YES here points at WHICH piece to extract)
[ ] Do two or more teams collide in the same module every week?
[ ] Does one component need 20x the capacity of the rest?
[ ] Does one component need a different release cadence or compliance scope?
[ ] Is one component's failure currently taking down unrelated features?

DECISION RULE
No pressure items      -> stay a (modular) monolith.
Pressure, not ready    -> fix the readiness gaps first.
Pressure and ready     -> extract ONE service, run it for a quarter, then decide.

Common mistakes and the symptoms they cause

  • Splitting by technical layer (a UI service, a business-logic service, a database service). Symptom: every feature needs changes in all of them, so deploys are coupled anyway.
  • Splitting before the domain is understood. Symptom: boundaries move every month, and each move means rewriting APIs and migrating data between services.
  • A shared database. Symptom: a column rename in one service breaks three others at runtime, and nobody can tell which code reads which table. You have a distributed monolith.
  • Skipping the operational basics. Symptom: an outage page reads "something is slow" and nobody can say which of twelve services is responsible. Centralised logs and tracing come before the split, not after.
  • Too many tiny services. Symptom: a team of nine owns fourteen deployables, and onboarding takes weeks because understanding one feature means reading five repositories.

How this shows up in real projects and interviews

System design interviews reward candidates who state a default and defend it: start with a modular monolith, extract services when a specific pressure appears, and name that pressure. Saying "microservices" as an automatic answer is a weaker signal than explaining what you would give up. On real teams, the useful skill is often migrating gradually: pick the module with the clearest boundary and heaviest pain, extract it behind its existing interface, and keep the rest alone. For more on practising this style of reasoning, see the system design interview guide.

Try it yourself

Take a system you know (a past project is fine) and fill in the checklist above. Write one paragraph deciding between staying put, fixing readiness gaps and extracting one named service, and list the single measurement that would change your mind.

Show a hint

Pick the PRESSURE item with evidence behind it, such as merge conflicts in one directory or one endpoint consuming most of the CPU, rather than a hypothetical future need.

Key takeaways

  • A monolith, modular monolith and microservices differ by what you deploy and who owns the data, not by how many lines of code exist.
  • Each synchronous network hop adds a failure point; chained availabilities multiply, so every extra hop needs a timeout and a plan.
  • Services deliver independent deploys, scaling and team autonomy only if you already have automated delivery, observability and clear domain knowledge.
  • Default to a modular monolith and extract one service at a time when a concrete pressure appears.

Test yourself

A trick from this lesson's ride: Count the things you release and own data for, not the lines of code you wrote. Four more tricks like it wait along the route, each followed by a question.

Ride this lesson

Frequently asked questions

When should you use microservices instead of a monolith?

Use microservices when several teams are blocked by a shared codebase, a component needs very different scaling or release cadence, and you already have automated delivery and observability. For a small team or an unclear domain, a modular monolith is usually the safer choice.

What is a modular monolith?

It is a single deployable application whose internals are split into modules with enforced boundaries: each module exposes a small interface and owns its own data. You get simple deployment and in-process calls, and you can extract a module into a service later if needed.

Are microservices faster than a monolith?

Not by default. A call across the network is slower and less reliable than an in-process call. Microservices can improve how quickly teams ship and let you scale hot components independently, but they do not make an individual request faster.