Library
10 min read

Monolith vs microservices

Published

A monolith is one application: one codebase, built and deployed as one unit, usually with one database. Microservices split the system into many small applications, each owning its own data, deployed independently and talking over the network. A modular monolith sits in between: one deployable, but split inside into modules with strict boundaries, as if they were services that happen to share a process.

The honest framing is not "old versus modern". Microservices solve an organisational problem (many teams stepping on each other in one codebase) and pay for it with a technical one (a distributed system). Whether that trade is worth it depends mostly on how many people you have.

Your answer

A six person startup is about to build a food delivery app. The CTO wants twelve microservices from day one: users, restaurants, menus, orders, payments, delivery, and so on. What would you say, and what would you suggest instead?

Three shapes for the same app
Function calls instead of network calls, one transaction across everything, one thing to deploy and debug. It scales further than people expect: run 20 copies behind the load balancer. It strains when 80 engineers share it and every deploy carries everyone's changes.

What microservices actually buy#

  • Independent deploys. The payments team ships ten times a day without coordinating with the search team. This is the main benefit, and it only matters once there are several teams.
  • Independent scaling. The image resizing service runs on 200 GPU nodes; the admin service on two small ones.
  • Fault isolation, if designed for it. A crash in recommendations does not take down checkout (but a synchronous call chain can make it do exactly that).
  • Technology freedom per service, which is a benefit in moderation and a hiring and maintenance cost in excess.

What they cost#

  • The network. A function call takes nanoseconds and never half fails. A network call takes a millisecond or more, can time out, can succeed without you knowing (see idempotency keys), and needs retries, timeouts and circuit breakers.
  • Data. Each service owns its database, so "place an order and charge the card" is no longer one transaction. You need sagas, outboxes and eventual consistency.
  • Operations. Deploy pipelines, dashboards, alerts, on call, dependency upgrades and security patches, multiplied by the number of services. Distributed tracing becomes mandatory (see logs vs metrics vs traces).
  • Availability compounds. A request that passes through several services synchronously is only up when all of them are.
One request, many synchronous hops
6
3 ms
99.9%
Added latency from hops alone6 x 3 ms = 18 ms
Availability of the whole path99.90%^6 = 99.401%
259 min
of failed requests per month, before you have written a bug

Each service looks reliable on its own dashboard, and the user experiences the product of all of them. Keep synchronous chains short, and move work that can wait onto events.

Teams that went back

In 2023 the Amazon Prime Video team wrote that moving one audio and video monitoring tool from a distributed design (Step Functions and Lambda) back into a single process cut its infrastructure cost by about 90%. Segment published "Goodbye Microservices" in 2018 after 140 small services became harder to run than the problems they solved. Shopify runs one of the largest Rails monoliths in the world and invests in making it modular rather than splitting it up. None of these say microservices are wrong. They say the boundary has to earn its cost.

The modular monolith, and how to split later#

A modular monolith gets you most of the design benefit for little of the cost. Group code by business capability (orders, payments, catalogue), give each module a public interface, forbid reaching into another module's internals or tables, and enforce it with tooling (ArchUnit in Java, Packwerk in Ruby, lint rules on imports in TypeScript). Each module owns its tables even though they live in one database.

When a module genuinely needs to become a service (a separate team, wildly different scaling, a different runtime), the strangler fig approach lifts it out gradually: put its interface behind a network call, route traffic to the new service piece by piece, and delete the old code when nothing calls it. Good boundaries in the monolith make this a week, not a year.

Conway's law is the rule underneath all of this: systems end up shaped like the teams that build them. Draw service boundaries where team boundaries are, or will be.

ChoiceWhat you gainWhat you payPick it when
MonolithFastest to build and change, one transaction, one deploy, easy debugging.Coupling creeps in without discipline; deploys and builds slow down as the team grows.New products and small teams. Most companies for longer than they think.
Modular monolithClear ownership and boundaries without network costs. A clean path to services later.Needs enforced rules; one deploy and one scaling unit for everything.Growing teams (roughly 10 to 50 engineers), or as the default starting shape.
MicroservicesIndependent deploys, scaling and ownership for many teams.Distributed system costs: network failures, no shared transactions, heavy operations.Many teams, parts with very different scaling or reliability needs, and a platform team to carry the tooling.
Names you will hear
  • KubernetesWhere most microservices run
  • IstioService mesh: mTLS, retries, traffic shifting
  • Apache KafkaEvent bus between services
  • OpenTelemetryOpenTelemetry for tracing across services

In the interview#

Interviewer
Would you build this as microservices?
Asked in most design rounds. "Yes, obviously" is a weak answer.
You
I would draw the logical services, because that is how we reason about ownership and scaling: orders, payments, catalogue, delivery tracking. Whether each is a separate deployable depends on the team. With one or two teams I would ship a modular monolith with those as enforced modules, and split out the parts with different scaling profiles first, here the delivery location ingestion, which takes a write every few seconds from every rider and looks nothing like the rest.
Separates logical design from deployment, and splits only where a concrete reason exists.
Interviewer
Orders and payments are separate services. How do you keep them consistent?
You
No distributed transaction. A saga: the order is created as pending, an event triggers the payment, and the payment result event moves the order to confirmed or cancelled, with compensating actions on failure. Events go out through a transactional outbox, and every handler is idempotent.
Split it out or keep it in?
Video transcoding that needs GPUs and runs for minutes per job
User profile and user settings
Payments, owned by a separate team with PCI compliance rules
A "utils" service for date formatting

Checkpoint#

Checkpoint

1. What is the main benefit microservices provide?

2. Six services, each 99.9% available, are called in sequence. Roughly how available is the request?

3. What makes a modular monolith different from a plain monolith?

Worth memorising
  • Microservices trade an organisational problem for a distributed systems problem.
  • Benefits: independent deploys, scaling, ownership. Costs: network, data consistency, operations.
  • Synchronous chains multiply failure. 0.999^6 is about 99.4%.
  • Modular monolith first; strangler fig to split along proven boundaries.
  • Conway's law: services follow team lines.
Say this in 60 seconds

A monolith is one deployable, usually with one database: fast to build, one transaction, simple to debug, and it scales horizontally further than people expect, but coupling and shared deploys hurt as teams grow. Microservices split the system into independently deployed services that own their data; they buy independent deploys, scaling and ownership for many teams, and cost network failures, no shared transactions, sagas and outboxes, heavy operations and compounding availability across synchronous hops. A modular monolith keeps one deployable but enforces module boundaries, public interfaces only and tables per module, which gets most of the design benefit cheaply and leaves clean seams to extract services later with the strangler fig pattern. I draw logical services in a design, deploy them as a modular monolith for small teams, and split out only parts with a concrete reason: a separate team, very different scaling, or a security boundary.