Monolith
The default that more systems should have stayed — what a monolith actually is, what it actually costs, and the failure mode of leaving it too late
Monolith
A monolith is a system deployed as a single executable artifact. One codebase, one build, one process running on each server, one database in the default shape. By definition it has no network calls between components, no service boundaries to maintain, no inter-service contracts to version.
For most of software's history this was simply "how you build a system." The word "monolith" only became distinct around 2014 — coined retroactively to contrast with "microservices" — and immediately accreted negative connotations. This page is a deliberate counterweight: a monolith is not legacy by default, it is not unsophisticated, and it is the right answer for more new systems than the marketing of distributed architectures suggests.
What a Monolith Is
A monolith is defined by how it is deployed, not by how it is internally organized. The key properties:
- One deployable artifact. Building the application produces one binary, one container image, one JAR, one Python package.
- One process per instance. Replicas exist for scale and HA, but each replica is the whole application.
- In-process function calls between components. No network hop within the system.
- Shared address space. All components see the same memory, same connection pools, same caches.
- One database, usually. Multiple tables, one schema, transactions span everything.
Internal structure can be anything — a single giant file, well-modularized packages, a clean hexagonal architecture, a layered design. The architecture style is orthogonal to the deployment style. A monolith can be beautifully organized internally; a microservices system can be a tangled mess internally per-service.
What People Actually Mean by "Monolith"
The term gets used to mean several different things, often without distinction:
| Sense | What it refers to | Same as monolith? |
|---|---|---|
| Deployment monolith | One artifact, one process | Yes (the literal sense) |
| Modular monolith | Deployment monolith + strict internal boundaries | Yes (an organized monolith) |
| Big ball of mud | No internal boundaries; tangled coupling | A monolith can be this, but does not have to be |
| Distributed monolith | Multiple services that must deploy together | Anti-pattern, not a monolith |
| Legacy / old | A pejorative association | Unrelated to actual properties |
The first two are the design choice this page is about. The third is a failure of internal engineering — solvable without changing deployment topology. The fourth is what happens when you try to be a microservices system but skip the work of independence. The fifth is folklore.
What Monoliths Are Good At
A monolith gives you, for free, many properties that microservice systems work hard to recover:
- Atomic transactions. Multi-table writes are a regular database transaction. No saga, no 2PC, no outbox pattern.
- Synchronous calls with low latency. A function call is nanoseconds. The equivalent service call is milliseconds, plus serialization, plus retry logic.
- One observability story. One log stream, one metrics namespace, one tracer. Finding the root cause of an error is a stack trace.
- One deployment. Compatibility between components is a compile-time check, not a runtime guess.
- Refactoring across the whole codebase. Renaming a function, restructuring a module, updating an interface — all atomic with a single review.
- One stack to operate. One language, one runtime, one set of dependencies, one set of security patches.
- Easy local development. Run the whole system on a laptop. Onboarding a new engineer is a clone-and-run.
These are not minor. Many production systems pay a substantial complexity tax to recover even partial versions of them.
What Monoliths Are Bad At
The constraints that justify splitting are real but specific:
- Scaling the wrong unit. If only one component needs more capacity (image processing, search indexing), scaling the whole monolith is wasteful. You pay memory and startup cost for everything, just to get more of one thing.
- Independent deployment. Every change deploys the whole system. A 30-line fix shares its release window with everyone else's work.
- Team coordination overhead. Many engineers committing to one codebase means coordination on merges, releases, and breaking changes. This scales sub-linearly with team size — at some point the meeting cost dominates the engineering cost.
- Long build and test times. A monolith's test suite grows with the codebase. A 30-minute test run shared by 50 engineers is real money.
- Stack constraints. All components must be in the same language and runtime. Adopting a new technology means rewriting or sidecar-ing.
- Blast radius. A memory leak or crash in any component takes down the whole system.
Notice what is not on this list: "you cannot use a monolith at scale" is not true (Stack Overflow, Shopify, GitHub, Basecamp, and others run very large monoliths in production). The constraints above are real but not absolute thresholds; they are pressures that build over time.
When to Stay a Monolith
Almost always at the beginning. Specifically:
- Early-stage product, unclear domain boundaries. Splitting prematurely freezes the wrong boundaries in place. Bounded contexts emerge from doing the work, not from pre-design.
- Team size below ~30 engineers. Below this, the coordination overhead is manageable. Above it, pressure to split increases.
- Tightly coupled data model. Your "user" entity is referenced by every table. Splitting along that fault line costs more than it saves.
- Low traffic / generous capacity headroom. Vertical scaling and a monolith handles surprising amounts of load. Premature distribution costs more.
- No independent scaling needs. All components grow together.
Within "stay monolith," the next decision is how to organize internally. A poorly organized monolith does have the failure modes that justify splitting; a well-organized modular monolith keeps most of the trade-offs in your favor for much longer.
When a Monolith Breaks Down
The signals are operational, not architectural. When to Split treats this question in depth; the headline ones:
- Deployment fear. Every release is risky. Releases happen weekly instead of multiple times per day. Engineers schedule deployments. This is friction the size of an org chart.
- Build / test cycle exceeds patience. Engineers commit and walk away, return to merge conflicts. Local development uses sampling rather than the full suite.
- Coupling-based incidents. A change in module A breaks module B in unrelated ways. Cause-and-effect across the codebase becomes unpredictable.
- Independent rollback impossible. Reverting a single feature requires reverting everything since the last good deploy.
- Capacity tuning is by guesswork. "Why is the system slow?" The answer is one of two dozen components in shared memory.
- Onboarding takes weeks. New engineers cannot get a useful change reviewed for a month because the system is too entangled to learn quickly.
When these symptoms accumulate, the answer is not necessarily microservices. The first move is usually to clean up internal boundaries, refactor toward a modular monolith, and split off one component that has the clearest case for autonomy. If even that does not relieve the pressure, then proceed to fuller microservices.
The Monolith-First Argument
Martin Fowler's "MonolithFirst" essay (2015) and Sam Newman's Building Microservices both make the case: start with a monolith, even if you eventually intend microservices.
The reason: you do not know the right service boundaries until you have built the system. Splitting before the domain is understood freezes the wrong cuts. A monolith with good internal boundaries can be sliced later when boundaries are clear; a microservices system with wrong boundaries needs costly re-cutting.
Greenfield projects that begin as microservices, in the experience of practitioners who have watched both, fail more often than greenfield monoliths that later split.
Common Mistakes
- Confusing "monolith" with "no architecture." A monolith can and should have layers, modules, dependency direction. Internal architecture is independent of deployment topology.
- Splitting before domain clarity. Three months in is too early; you do not know your bounded contexts yet. The full list of splitting mistakes lives in Anti-Patterns.
- Splitting to "look modern." Microservices are sold as a marker of engineering maturity. They are a marker of one specific set of trade-offs that may or may not be yours.
- Believing scale forces microservices. It does not. The largest sites in the world include monoliths. Scale increases the pressure but does not mandate the solution.
- Treating monolith as a temporary state. Many monoliths should stay monoliths for their entire lifetime. That is not a failure.
Further Reading
- Fowler, MonolithFirst (2015) — the foundational essay arguing for starting with a monolith.
- Sam Newman, Building Microservices (2nd ed., 2021), Chapter 1 — surprisingly the best treatment of monoliths is in the microservices book.
- David Heinemeier Hansson, The Majestic Monolith (2016) — the Basecamp argument for staying monolithic.
- Shopify Engineering, Deconstructing the Monolith (2019) — the largest production Rails monolith, and what they have learned operating it.
- Robert Martin, Clean Architecture (2017) — how to organize a monolith well, independent of any "should I split" question.
Pre-commit Checklist
- Am I splitting because of a measured operational problem, or because microservices sound modern?
- If I am staying monolith, are my internal boundaries good enough that I could split if I had to? (See Modular Monolith.)
- Has my team actually hit the pain that justifies splitting, or am I designing for hypothetical future scale?
- Have I considered splitting only one component, instead of fully decomposing?
- For each "monolith problem" I am citing as the reason to split, can I find a smaller fix that does not require deployment-topology changes?