Every growing engineering org eventually has the same argument: should our code live in one repository or many? The debate resurfaces every time a new service gets spun up, a shared library needs updating in three places at once, or someone reads a blog post about how Google or Facebook does it and asks why we don’t. The honest answer is that both models work at real scale, both models have well-documented failure modes, and the right choice depends on facts about your team that have nothing to do with which company wrote the most-shared engineering blog post this year.
This is not a question with a universally correct answer, and treating it that way is how teams end up copying Google’s repository strategy without Google’s tooling, or splitting a five-person startup into twelve repos because “microservices.” Here’s what the trade-off actually looks like, what breaks first in each model, and how to reason about which one fits the team you actually have.
What’s the actual difference between a monorepo and a polyrepo?
A monorepo is a single version-control repository that holds the source for multiple projects, services, or libraries, typically with unified versioning and a shared build system that understands how the pieces depend on each other. A polyrepo is the more traditional arrangement: each service, library, or application lives in its own repository with its own history, its own release cadence, and its own access controls.
The distinction is not about how many services you run — you can run a hundred microservices out of one monorepo, or three services split across three repos. It’s about where the code lives and how changes to shared dependencies propagate. In a monorepo, updating a shared library and updating every caller can happen in a single atomic commit. In a polyrepo, that same update means publishing a new package version and then opening separate pull requests against every consumer, each on its own schedule.
Neither model is new. Polyrepo is simply what you get by default when you create a new Git repository per project, which is why it’s the more common starting point. Monorepos require deliberate investment — in tooling, in build systems, in access-control design — which is exactly why they tend to show up at companies that grew large enough to feel the specific pain a monorepo solves.
Why do companies like Google and Facebook use monorepos at that scale?
Google’s repository is the most extensively documented monorepo in the industry. In a 2016 paper for Communications of the ACM, Google engineers Rachel Potvin and Josh Levenberg described a single repository holding roughly two billion lines of code across nine million source files, with a commit history spanning the company’s entire existence, used by the vast majority of Google’s engineers (Potvin & Levenberg, CACM 2016). The stated benefits are unified versioning, extensive code sharing, simplified dependency management, atomic cross-project changes, and the ability to do large-scale refactoring across the entire codebase in one pass.
That last point is the one people underrate. In a true monorepo, renaming a widely used function or upgrading a core library version is a single coordinated change, verified by one build, rather than a slow-motion migration tracked in a spreadsheet across forty repositories. Code visibility is the other underrated win — any engineer can find and read any other team’s code, which lowers the cost of understanding how a dependency actually behaves instead of trusting its documentation.
Facebook took a similar direction but hit a different bottleneck: their existing tools couldn’t handle a single repository at their scale. Rather than build new version control from scratch, Facebook’s engineering team invested in scaling Mercurial, largely by making status and diff operations aware of filesystem-change notifications through their file-watching tool, Watchman, which they reported made status checks several times faster than stock Git at their repository size (Engineering at Meta, 2014). The lesson generalizes: the monorepo model doesn’t fail because “one big repo” is a bad idea, it fails when the tooling underneath it wasn’t built for the scale you’re asking it to handle.
What are the real advantages of a polyrepo approach?
Monorepo case studies get shared more often, which creates a skew — polyrepo has genuine, unglamorous advantages that matter for a lot of teams.
Clean ownership boundaries. A repository per service maps naturally onto a team per service. Access control is trivial: give the payments team write access to the payments repo and nothing else. In a monorepo, replicating that granularity requires deliberate path-based permission tooling that most small-to-mid-size teams don’t need to build.
Independent release cadence. Each repo ships on its own schedule with its own versioning. A library team can cut a breaking 2.0 release without coordinating a company-wide migration deadline — consumers upgrade when they’re ready, the same way you’d consume any external open-source dependency.
Smaller, faster tooling out of the box. git clone, git log, and CI checkouts on a small repo are simply fast, with no special configuration. A monorepo at real scale needs shallow clones, sparse checkouts, or a custom version-control layer to keep those same operations fast — Google and Facebook both built substantial internal tooling specifically to solve this. A five-repo startup gets the speed for free.
Simpler mental model for small teams. When a team is small enough that everyone already knows where everything lives, the coordination problems a monorepo solves don’t exist yet, and the tooling investment it requires has no payoff.
Polyrepo’s real weakness is the mirror image of monorepo’s strength: shared-dependency changes become distributed coordination problems. A security fix in a shared library means bumping the version, then chasing down every consumer to actually adopt it — and in practice, some consumers lag for months.
What breaks first as a team grows, in each model?
In a polyrepo setup, the first real pain shows up as dependency drift. Different services end up on different versions of the same internal library because nobody was forced to upgrade together, and eventually a bug fix has to be manually backported across several stale versions at once. The second failure mode is duplicated infrastructure — every repo grows its own copy-pasted CI config, its own linting setup, its own release scripts, each drifting slightly from the others until nobody can say with confidence which repo does things “the right way.”
In a monorepo, the first real pain is build and CI time. Without incremental, dependency-aware build tooling, every change triggers a full rebuild and full test run of everything, and that cost grows with the whole codebase rather than with the size of the change. The second failure mode is blast radius — a bad commit to a widely depended-on piece of shared code can break builds across the entire company simultaneously, which is why monorepo teams invest heavily in pre-merge validation rather than relying on catching problems after the fact. That validation discipline overlaps directly with good code review practice: the smaller and more focused a change to shared code is, the easier it is for CI and reviewers to catch the blast-radius problem before it merges.
What tooling does a monorepo actually require?
This is the part teams underestimate when they decide to consolidate repos. A monorepo without the right build system is just a very large, very slow polyrepo with extra steps.
The tooling that makes a monorepo workable does three things: it figures out what actually changed, it only rebuilds and retests the parts affected by that change, and it caches results so identical work is never repeated. Nx documents this directly as the core reason dedicated monorepo tooling exists — without affected-project detection and caching, CI time scales with the size of the entire repository rather than the size of a given change, which becomes untenable well before a codebase reaches Google-scale (Nx documentation). Comparable tools in this space (Turborepo, Bazel, Buck) all solve the same underlying problem with different trade-offs in setup complexity and language support.
The other piece is workspace-level dependency management — npm/Yarn/pnpm workspaces for JavaScript, or the equivalent module system for other ecosystems — so that internal packages can depend on each other without publishing to a registry for every internal change. Get this wrong and a monorepo just becomes several projects that happen to share a .git folder, with none of the atomic-change or unified-tooling benefits that justified the migration.
How do you actually decide?
Start from your team’s real coordination cost, not from a case study. A few honest questions cut through most of the debate:
How often do you change something that multiple projects depend on? If shared-code changes are rare, polyrepo’s independent-release model is a genuine advantage, not a limitation to route around. If they’re constant, a polyrepo’s version-bump-and-wait cycle is actively slowing you down.
Do you already have — or are you willing to build — incremental build tooling? A monorepo without it will feel slower than the polyrepo you left, not faster. If nobody on the team wants to own that tooling investment, don’t adopt the model that requires it.
How many teams, and how sharp are the ownership lines? Clean, stable ownership boundaries between teams make polyrepo’s access-control simplicity a real win. Fuzzy or overlapping ownership — several teams routinely touching the same code — is exactly the situation a monorepo’s shared visibility and atomic changes were designed for.
What’s your actual scale, today, not aspirationally? Google and Facebook’s tooling exists because they operate at a scale most organizations never reach. Adopting monorepo practices — atomic cross-project commits, shared code visibility, a single CI pipeline — is worth considering broadly. Adopting a specific company’s scale of tooling investment before you have their scale of problem is a common and expensive mistake.
Most teams land somewhere in between: a handful of related services and their shared libraries in one repository, with genuinely independent products kept separate. That hybrid is not a compromise so much as it’s the honest application of the same underlying question — how often does this code change together — applied at the level of individual projects rather than the whole company. Good repository hygiene at any scale still comes down to the same discipline covered in managing code and snippets well: a clear home for things, a shallow structure, and a team that actually knows where to look.
Frequently Asked Questions
Is a monorepo the same thing as a microservices monolith?
No. Monorepo versus polyrepo describes where source code lives; microservices versus monolith describes how the software is deployed and run. You can deploy a hundred independently running microservices from a single monorepo, or ship one deployed monolith built from code spread across a dozen repositories. The two decisions are independent and often get conflated.
Do small teams need monorepo tooling like Nx or Bazel?
Usually not right away. Dedicated monorepo build tooling earns its cost when a change to shared code needs to trigger rebuilding and retesting only the affected parts of a large codebase. A small team with a handful of services can often manage with plain workspace tooling (npm/Yarn/pnpm workspaces) and add dedicated build tooling only once CI time or coordination overhead becomes a measurable problem.
What’s the biggest risk of switching from polyrepo to monorepo?
Migrating without first solving the build-and-CI problem. Consolidating several repositories into one, without incremental build tooling that only rebuilds what a change actually affects, tends to make everyday development slower rather than faster — the opposite of the intended outcome. Treat the tooling investment as a prerequisite to migration, not a follow-up task.
Can you mix monorepo and polyrepo within the same company?
Yes, and many organizations do. A common pattern keeps closely related services and their shared libraries in one repository — where atomic cross-project changes matter — while keeping genuinely independent products or open-source components in their own repositories. The decision is best made per cluster of tightly coupled code, not as one company-wide rule, and it can evolve as local development and CI tooling mature.
