Monoliths, microservices, and distributed systems
In the previous lesson, the coffee product’s backend appeared as one box on the diagram, and now it is time to open that box and ask a foundational question.
Should the backend be one application, or should it be divided into several smaller applications?
This is one of the most discussed decisions in software architecture, and it has vocabulary you will hear constantly: monoliths, microservices, and distributed systems.
What is a monolith?
A monolithConcept · lights on your mapmonolithA single application containing most or all of the backend’s functionality: one codebase, built and deployed as one unit. Internal parts call each other as ordinary function calls. Simple to build, test, and reason about; the starting shape of most successful products. is a single application that contains most or all of a product’s backend functionality.
The coffee company’s monolithic backend might contain:
- Ordering logic
- Payment coordination
- Account management
- Loyalty points
- Notification handling
All of it lives in one codebase and is built and deployed as one unit.
When the ordering logic needs the loyalty logic, it does not send a network request but calls a function in the same running program.
The word monolith is sometimes said with an eye-roll, as though it names an outdated design.
But that reputation is not deserved.
The monolith is not a mistake
Monoliths have real strengths.
They are commonly:
- Simpler to build, because everything lives in one place
- Simpler to test, because one running application contains the whole backend
- Simpler to deploy, because there is one thing to release
- Simpler to debug, because one process handles the entire request
- Simpler to keep consistent, because one application often uses one database and one transaction can cover an entire operation
Internal communication is also fast and reliable. A function call inside one process does not travel across a network, so it cannot be lost in transit, delivered twice, or delayed on the way.
Most successful products started as monoliths, and many large, profitable products still run as monoliths today.
A useful principle is: a monolith is a valid architecture, not a phase to be ashamed of.
Monoliths can still scale
A common misconception is that a monolith cannot handle growth, but in Module 8 you learned that a backend can run as many identical instances behind a load balancer.
A monolith can do exactly that. During the dinner rush, the coffee company can run ten copies of the whole backend instead of two.
The limitation is granularity.
If only payment processing is under heavy load, the company must still replicate the entire application (ordering, loyalty, notifications, and all) to add payment capacity.
The monolith scales as one unit, and it cannot scale one internal capability by itself.
Where monoliths begin to hurt
The monolith’s weaknesses usually appear as the product and the organization grow.
Common pressure points include:
- Team collisions. Forty engineers editing one codebase coordinate constantly, and changes interact in surprising ways.
- Coupled releases. One deploy ships everyone’s changes together. A bug in one team’s feature can delay or break every team’s release.
- Coupled scaling. The whole application must be replicated to scale any part of it.
- Shared blast radius. A crash, memory leak, or runaway bug in one area can take down the entire backend, because everything runs in the same process.
- One technology stack. The whole backend generally shares one language and framework, even if a particular problem would fit another tool better.
- Slowing feedback. Builds and test suites grow with the codebase, and every change waits on all of them.
None of these problems is fatal on day one, but they accumulate with size, of the codebase, of the traffic, and especially of the team.
What are microservices?
MicroservicesConcept · lights on your mapmicroservicesAn architecture that divides the backend into multiple smaller services (each with its own codebase, deployment, and often its own data) communicating through internal APIs. Buys independent releases, scaling, and ownership at the price of operating a distributed system. are an architectural approach that divides the backend into multiple smaller services, each responsible for a particular capability.
The coffee backend might become:
- An order service
- A payment service
- An account service
- A loyalty service
- A notification service
Each service has its own codebase and is deployed independently. When one service needs another, it sends a request to that service’s internal API, Module 6’s machinery, aimed inward at the company’s own systems.
Each service also commonly owns its data. The loyalty service manages the loyalty database, and other services ask the loyalty service rather than reaching into its tables directly. Sharing one database between services is possible, but it quietly reconnects the pieces the split was meant to separate.
Here are the two shapes side by side:
What microservices buy
Splitting the backend purchases several kinds of independence.
- Independent releases. The loyalty team can deploy on Tuesday without waiting for the payment team, and a bug in one service does not freeze every other team’s release.
- Independent scaling. If payments are under load, the company scales the payment service alone rather than replicating the entire backend.
- Clear ownership. One team can own one service end to end, its code, its deploys, its behavior in production.
- Failure boundaries. A crash in the loyalty service does not necessarily crash ordering, because they run as separate processes on separate infrastructure.
- Technology freedom. Each service can, in principle, use the language or tools that fit its job.
Notice what every item on that list has in common. Each one matters most when many people are working on the same product.
So microservices are, at their core, an organizational tool as much as a technical one.
What is a distributed system?
The price of the split is that the backend becomes a distributed systemConcept · lights on your mapdistributed systemA system whose parts run on different machines and cooperate over a network. Parts can fail independently, messages can be delayed, lost, or duplicated, and two parts can temporarily disagree about the current truth. That is the source of many of engineering’s genuinely hard problems.: a system whose parts run on different machines and cooperate over a network.
Strictly speaking, the coffee product was already somewhat distributed (the backend and its database usually run on different machines), but microservices multiply the degree: many services, many machines, many network conversations for a single user action.
Distribution introduces problems that do not exist inside one process:
- Parts can fail independently, so the system must handle partial failure
- Network calls can be slow, lost, or delivered more than once
- Two services can temporarily disagree about what just happened, because each holds its own copy of state
- A single user request may cross several services, making problems harder to trace
This is Module 1’s source-of-truth problem multiplied by the number of boxes, and Module 6’s timeout, retry, and idempotency lessons applied to the company’s own internal traffic.
Function calls become network calls
Veteran engineers compress this entire tradeoff into one sentence: “you’re trading function calls for network calls.”
It is worth unpacking exactly what that trade means:
Inside a monolith:loyalty.add_points(order)Between microservices:POST /loyalty/pointsInside a monolith, the unhappy paths of internal communication barely exist.
Between services, every internal interaction inherits the full unhappy-path catalog from Module 6: timeouts, retries, duplicate deliveries, and the idempotency discipline that makes retries safe.
The whiteboard gains arrows, and as the previous lesson showed, arrows are where failures travel.
The operational bill
Microservices also carry a cost that appears off the whiteboard.
Each service needs its own:
- Deployment pipeline
- Monitoring and alerts
- Infrastructure and scaling configuration
- On-call attention when it misbehaves
Internal APIs between services also become contracts between teams, the same contract discipline from Module 6, now applied inside the company, and because changing a service’s API can break the services that depend on it, versioning and coordination reappear internally.
Testing becomes harder too. Verifying one service is straightforward, but verifying that twelve services cooperate correctly is not.
A team choosing microservices is choosing to operate many small applications instead of one large one. That is only a good trade when the many-applications problems hurt less than the one-application problems did.
Failure isolation is designed, not automatic
One caveat deserves its own section.
Splitting the backend does not automatically contain failures.
Suppose the order service calls the payment service and waits for the response. If the payment service hangs, the order service’s requests pile up waiting, and checkout fails anyway. The failure crossed the service boundary along the dependency arrow, exactly as the previous lesson predicted.
Real isolation requires the practices you have already met: timeouts, limited retries, fallbacks, and designs that let unaffected features keep working.
A useful principle is: service boundaries create the possibility of failure isolation. Engineering discipline creates the reality.
Between the extremes
Monolith and microservices are endpoints on a spectrum, not a binary choice.
Real systems commonly sit in between:
- A modular monolith keeps one deployable application but organizes the code internally into well-separated modules with clear boundaries, much of the discipline, little of the network.
- A company may run a monolith plus a few extracted services, for example, splitting out only the notification work that was slowing everything else down.
- Services can be coarse. Splitting into four services is a very different commitment from splitting into forty.
A common and well-regarded evolution starts with a monolith, keeps its internal boundaries clean, and extracts a service only when a specific, concrete pain justifies it.
The industry learned this the expensive way. After a decade of enthusiasm for splitting everything, the folk wisdom settled into: split when the pain of staying together exceeds the pain of being apart.
Teams shape architecture
There is a famous observation in software engineering, known as Conway’s law. Organizations tend to design systems that mirror their own communication structure.
Four teams that rarely talk to each other will naturally produce four loosely connected systems. One tight-knit team will naturally produce one integrated system.
This is why the monolith-versus-microservices question is as much about the organization as the technology.
Questions that actually decide it include:
- How many engineers work on this backend, and on how many teams?
- Are teams genuinely blocked by shared releases?
- Does one part of the system have very different scaling needs?
- Can the organization operate many deployment pipelines and on-call rotations?
- Is there a specific pain, or only a fashionable diagram?
Five engineers with a monolith usually move faster than five engineers babysitting twelve services. Five hundred engineers in one codebase slow to a crawl, unless the company invests heavily in internal walls and tooling, which is exactly what the giants who stay on monoliths do.
The mental model to remember
A monolith is a single application containing most or all of the backend, built and deployed as one unit.
Monoliths are simple to build, test, deploy, and debug, and they can scale by running many identical copies, though only as one unit.
Microservices divide the backend into independently built, deployed, and scaled services that communicate through internal APIs.
Microservices buy team independence, targeted scaling, and clearer ownership.
The price is operating a distributed system: parts on different machines, cooperating over a network, where calls can fail, messages can arrive twice, and components can temporarily disagree.
Failure isolation between services is possible but must be engineered with timeouts, retries, and fallbacks. Boundaries alone do not provide it.
The choice is a spectrum, and it is driven more by the team and its pains than by technology fashion. The industry’s hard-earned default: start together, split when it hurts.
You should now be able to explain what monoliths and microservices are, what each one trades away, and why the honest answer to “which is better?” begins with questions about the team.
A 6-person startup asks for advice: “Should we build our new product as microservices from day one? That's what Netflix does.” What is the seasoned answer?
▼ answer the check to continue ▼