'The Majestic Monolith'
David Heinemeier Hansson argued that a single, well-organised application serves small and medium teams better than a fleet of services, and that the costs of distribution are paid up front for benefits most teams never need.
Architecture Patterns · lesson 1 of 7 · about 26 min
The simplest shape and why to start there: a single deployable behind a client-server interface, what team growth does to a shared codebase and how modules help, scaling by cloning until the database is the limit, why big release trains are risky, how test time grows, when splitting genuinely pays, and why the big-bang rewrite almost never catches up. Tap any for a plain-language card.
The simplest shape
The pressure appears as the team grows. Brooks observed that coordination paths grow with the square of the headcount: n people have n(n−1)/2 pairs who may need to talk. In one codebase, more developers touching the same hot files means more merge conflicts and more stepping on each other. A modular monolith keeps one deployable but enforces boundaries inside (modules with clear interfaces and owners), which removes much of that friction without a network between the parts.
Grow the team from 8 to 40 in a single module: conflicts per week jump. Then add modules.
8 developers have 28 possible conversations. Each edits 5 files a week in 2,000 files across 1 module (30% of edits land on hot files): that is 0.9 overlapping edits a week, and 60% of weeks have at least one.
Seeded 400-week simulation: 5 file edits per developer per week, 30% of them on a hot 5% of files; developers are spread evenly over modules and edit within their own module.
Scaling the single program
The shared thing is usually the database. Ten app servers all talk to one database, so once the combined traffic they send exceeds what it can serve, adding the eleventh server adds nothing. The remedies are the earlier lessons: a cache that absorbs reads, read replicas, indexes, and finally sharding. Below, the app tier is 500 requests a second per server, the database serves 3,000 queries a second, and 80% of requests are reads.
With no cache, throughput flattens at 3,000 from about six servers. Put 80% of reads in a cache and the same database supports 8,000.
10 instances can serve 5,000 requests a second, but the shared database only lets 3,000 through (100% of requests reach it). Throughput is 3,000, limited by the database. More servers will not help: cache, replicate or shard.
Database load per request = 1 − 0.8 × cache hit share (80% of requests are reads). Throughput = min(instances × per-instance, database capacity ÷ database share).
Deploying the whole thing
The remedy is not microservices first; it is smaller, more frequent releases from the same monolith (trunk-based development, feature flags, canary deploys from the deployments lesson). Releasing every day with a handful of changes makes each release low-risk and easy to roll back. The same code, shipped continuously instead of on a fortnightly train, has far less drama.
At 40 changes every 14 days, 70% of trains are blocked. At daily releases with about three changes each, it is under 10% and a block delays only a day.
40 changes per release with a 3% defect rate each means 70% of releases contain at least one bad change. With a train every 14 days, a finished change waits 16.9 days on average. Shipping more often shrinks both.
P(bad release) = 1 − (1 − q)^changes. Delay = half the interval waiting for the train, plus a full interval whenever the train is blocked. The comparison rows keep the same overall change rate.
Feedback time
There is a middle path before splitting the deployment: make the build aware of structure. With clear module boundaries and a build tool that knows the dependency graph (Bazel, Nx, Gradle), a change to one module re-tests only the modules that depend on it. Parallelising tests across machines (sharding) helps too, at the price of more machines. Set the size of your suite.
200,000 tests at 100 ms each take about 22 minutes even on 20 machines, set-up included. Test only the affected 10% and it drops to under 7.
200,000 tests of 100 ms spread over 20 machines give 21.7 min of feedback (including 5 minutes of fixed set-up). Running only the 10% that depend on the change takes 6.7 min. Past about 15 minutes people stop waiting and start batching.
Time = tests × duration ÷ machines + 5 minutes of fixed set-up (checkout, build, start-up).
When to split
A practical rule is Conway's law: systems end up mirroring the communication structure of the organisation that builds them, so split along team boundaries, not technical layers, and only when the coordination cost of the shared codebase is hurting. Score your situation below; the result is a heuristic, not a verdict, but it shows which forces actually point toward splitting.
A team of 15 with no need for independent deploys scores low: stay modular. Raise the team to 60 and the need for independent releases and the score crosses into “split”.
15 engineers, independence somewhat, scaling skew somewhat, technology variety no, operations decent: score 3. Suggestion: stay a (modular) monolith. Weak operations push the score down on purpose, because many services need more tooling, not less.
A simple additive heuristic: team size (0 to 3 points) + the three needs (0 to 3 each) + twice the operations maturity − 2. It illustrates which factors matter, not a calibrated model.
Which move?
Stay simple until a specific pain justifies a more complex shape, and migrate in steps that each ship. Each answer is worth 20 XP.
Ask: what specific problem am I solving, and what is the smallest change that solves it?
A team of six is building an MVP and wants to move fast.
Tip: press 1 to 4 to answer, Enter for the next one.
Who lives by this
Four published views on monoliths and splitting.
'The Majestic Monolith'
David Heinemeier Hansson argued that a single, well-organised application serves small and medium teams better than a fleet of services, and that the costs of distribution are paid up front for benefits most teams never need.
a large modular monolith
Shopify has written about keeping its large Ruby on Rails application as a modular monolith, with enforced component boundaries and ownership, rather than splitting it into hundreds of services, to keep development fast while the team grew.
video-quality monitoring
Prime Video's engineering team described moving a video-quality monitoring tool from a distributed set of serverless components back to a single process, reporting a cost reduction of about 90% because the data passing between components was the expense.
the Strangler Fig application
Fowler described incrementally replacing a legacy system by growing the new one around it, routing more and more functionality to the new code until the old can be removed, named after the vine that gradually envelops a tree.
Interview angle
Six questions, each worth 20 XP. 'Would you use microservices?' is a standard architecture prompt; the strongest answers start with the monolith.
Say what concrete problem splitting solves, and what it costs.
Why is a monolith a good starting point?
Tip: press 1 to 3 to answer, Enter for the next one.
Break it
The proven alternative is the pattern: build new functionality (or re-implement one slice at a time) as new modules or services behind a routing layer, send traffic to them piece by piece, and let the old code shrink until it can be deleted. Every step ships to production and earns its keep; if the effort stops, you still have something better than you started with. Set the sizes.
With 500 features, the old system adding 20 a month and the rewrite shipping 25, parity takes 100 months. Raise the rewrite rate to 60 and it takes a year and a quarter.
The old system has 500 features and grows by 20 a month; the rewrite ships 25. It closes the gap by 5 a month, so parity takes 100 months (8.3 years) and the business sees no benefit until then. The strangler approach delivers value at every step instead.
Parity time = features ÷ (rewrite rate − old growth rate). A deliberately simple model that ignores learning, bugs and the features nobody remembers.
Where to go next