Skip to content

Monolith vs Microservices

Almost every successful system starts as a monolith, and for good reason. When you are figuring out what to build, the last thing you want is the overhead of a distributed system. You write your modules, wire them together in one process, point them at one database, and ship. To understand why a team would ever take on the complexity of microservices, you first have to understand the architecture they are moving away from — and why it served them so well for so long.

A monolithic architecture packages the entire application as a single deployable unit. All the modules — the order logic, the payment logic, the inventory logic — live in one codebase, compile or bundle into one artifact, run in one process, and share one database.

This is not a flaw. For a small team and a young product, the monolith has real strengths:

  • Simple to develop. One repository, one build, one way to run the whole thing on your laptop.
  • Simple to test. End-to-end tests run against a single process; there is no network in the middle to mock or to flake.
  • Simple to deploy. One artifact goes out the door. There is no version-skew between services to reason about.
  • Easy, strong consistency. A single database transaction can update orders, inventory, and payments atomically, and a single join can read across all of them.

The trouble is not the monolith itself — it is what happens to a successful monolith over time. As the codebase grows and the team grows with it, the very thing that made it simple, its single shared unit, becomes the thing that slows everyone down:

  • The codebase grows large enough that no one fully understands it, and changes in one area break another.
  • Every change, however small, requires building and deploying the entire application, so releases get large, infrequent, and risky.
  • You cannot scale parts independently. If only the payment logic is CPU-bound, you still have to run more copies of the whole application to keep up.
  • Teams step on each other. Many people committing to one codebase and waiting for one shared release pipeline creates contention and coordination overhead.
  • You are locked into the original technology stack; adopting a new language or framework means rewriting or carrying the whole monolith with you.

So the forces are clear: the monolith optimizes for early simplicity, but a large, busy monolith trades away independent deployment, independent scaling, and team autonomy.

The microservices architecture is the deliberate contrast. Instead of one deployable unit, you split the application into a set of smaller services, each independently deployable, each owning its data, each aligned to a business capability. What was a method call inside one process becomes an API call or an event across the network.

The diagram below puts the two side by side: one box you deploy as a whole, versus many boxes you deploy on their own.

flowchart TB
  subgraph Monolith[Monolithic Architecture]
    direction TB
    M[Single Deployable Unit]
    MO[Orders Module]
    MP[Payments Module]
    MI[Inventory Module]
    M --- MO
    M --- MP
    M --- MI
    MDB[(Shared Database)]
    MO --> MDB
    MP --> MDB
    MI --> MDB
  end
  subgraph Micro[Microservices Architecture]
    direction TB
    SO[Order Service] --> SODB[(Orders DB)]
    SP[Payment Service] --> SPDB[(Payments DB)]
    SI[Inventory Service] --> SIDB[(Inventory DB)]
  end
One deployable unit over a shared database, versus many independently deployable services each with its own database

The split is not free, and it is not automatically better. It exchanges the monolith’s in-process simplicity and easy consistency for independence and scalability — and it adds the cost of operating a distributed system. The next lesson weighs that exchange honestly.

What changes when you move from the left of the diagram to the right:

  • A single shared transaction becomes a coordinated workflow across services, so strong consistency becomes eventual consistency you design for.
  • A whole-application deploy becomes many small, independent deploys.
  • One scaling knob becomes one knob per service.
  • One codebase owned by everyone becomes many codebases, each owned by a team.

None of this means the monolith is wrong. A well-structured monolith — a modular monolith with clean internal boundaries — can carry a product a very long way, and is often the smarter place to start. The point of this lesson is not that one is good and the other bad, but that they make opposite trade-offs, and you should pick the trade-off your situation actually calls for.

MonolithMicroservices
Best fitSmall team, unclear domain, MVPLarge team, clear domain, high scale requirements
DeployAll togetherIndependent per service
ScaleThe whole systemOnly the service that needs it
DebugEasy — one stack traceHard — needs distributed tracing
TeamOne team, access to everythingOwnership split per service
ConsistencyStrong (ACID transactions)Eventual (you design for it)

Moving to microservices before the domain is clear — decomposing along the wrong boundary, then having to refactor. Symptoms:

  • The split-out services call each other synchronously for 80% of operations.
  • Every new feature touches several services at once.
  • Start with a modular monolith instead, and extract once the boundaries are clear.

Scaling microservices before you need to — provisioning for 1M users while you have 1,000. Symptoms:

  • The team spends 70% of its time on infrastructure, not the product.
  • The real problem isn’t scale yet — it’s feature velocity.

💡 Real-world examples

Stack Overflow: ran a monolithic .NET application throughout its growth — per statistics its former Architecture Lead Nick Craver published in 2016, the system served around 209 million HTTP requests per day (roughly 6 billion per month) from a small handful of web servers, showing how far a well-optimized monolith can go (Nick Craver: Stack Overflow — The Architecture — 2016 Edition).

Segment: in 2018 published “Goodbye Microservices,” describing how its per-destination architecture had grown to 140+ services, with 3 full-time engineers doing nothing but keeping the system alive — they consolidated back into a single monolithic service called Centrifuge. The lesson: microservices pay off when a team genuinely needs to scale, not because it’s the architectural fashion (Twilio/Segment Engineering: Goodbye Microservices).

What defines a monolithic architecture?
Which is a genuine strength of the monolith for a small team and young product?
When does a monolith typically start to hurt?