Photo by Chris Ried on Unsplash
The Monorepo Comeback: Why Big Codebases Are Converging Again
For most of the 2010s, the conventional wisdom on repository structure tracked the conventional wisdom on architecture: break things apart. Microservices meant separate deploy units, and separate deploy units seemed to imply separate repos. Each team got its own git history, its own release cadence, its own CI pipeline. It felt modular and clean.
Lately that assumption has been quietly reversing. Companies large and small are consolidating code into single repositories that hold dozens or hundreds of projects, and the tooling ecosystem around this pattern (Bazel, Nx, Turborepo, Buck2, Pants) has matured enough that “monorepo” no longer means “thing only Google can operate.”
Why Split Repos Get Painful
The case for separate repos per service or package is intuitive: isolation. A change in one repo can’t accidentally break another. Teams can choose their own tools and release schedules without coordinating.
In practice, that isolation comes with hidden coordination costs. A shared library that lives in its own repo has to be versioned, published, and bumped everywhere it’s consumed. Cross-cutting refactors, like renaming a widely used function or upgrading a framework, turn into a slow-motion campaign across dozens of pull requests, each waiting on its own review and release cycle. Dependency drift creeps in as different repos pin different versions of the same shared code, and nobody notices until something breaks in production.
Multiply that by an organization with hundreds of internal packages and the overhead of “keeping everything in sync” starts to rival the overhead the split was supposed to avoid.
What a Monorepo Actually Solves
A single repository doesn’t eliminate the need for boundaries between projects, it just changes where those boundaries live. Instead of a repo boundary enforcing isolation, you rely on build graph metadata, ownership files, and tooling to say which code depends on which.
The payoff is atomic changes. A developer can update a shared library and every consumer of it in one commit, verified by one CI run, instead of a chain of dependent releases. Code search and navigation become trivial because everything is in one place. And because the build tool can see the entire dependency graph, it can be precise about what actually needs to rebuild or retest when something changes, rather than relying on each downstream repo to remember to bump a version and rerun its own suite.
Why This Wasn’t Practical Before
The reason monorepos were a Google-and-Facebook-only pattern for so long is that naive tooling doesn’t scale to that shape of codebase. A repo with a million files and thousands of commits a day breaks assumptions baked into git itself, and a CI system that rebuilds and retests everything on every change becomes unusable once the codebase passes a certain size.
The tools that made monorepos viable outside a handful of hyperscalers all attack the same problem from different angles: build the dependency graph explicitly, cache build and test results by content hash rather than by branch or timestamp, and only execute the subset of work actually affected by a given change. Bazel popularized this with hermetic, reproducible build rules. Nx and Turborepo brought a lighter-weight version of the same idea to JavaScript and TypeScript ecosystems, where the pain of split repos and duplicated dependency trees had become especially visible. Remote caching, where a build artifact computed by one developer’s machine or CI run is reused by everyone else instead of recomputed, turns what used to be a full rebuild into an incremental one almost everywhere.
The Tradeoffs Don’t Disappear
None of this makes monorepos free. Access control gets harder when everything lives in one place and a team wants to restrict who can see certain code. Git itself still struggles at extreme scale, which is why the largest adopters use virtual filesystem layers or custom source control rather than stock git. And a monorepo doesn’t automatically enforce architectural boundaries; a team can still write tightly coupled spaghetti across “modules” that happen to sit in the same repo. The build graph gives you the tools to enforce boundaries, but somebody still has to configure and maintain that discipline.
The realistic takeaway is that monorepo versus polyrepo isn’t a values statement about microservices anymore. It’s a build tooling decision, and the tooling has gotten good enough that consolidating code is now a legitimate default rather than a scaling hack reserved for a few companies with unlimited infrastructure budgets.