Monorepo vs. Polyrepo: 2 Billion Lines in One Repo, and 140 Services That Drifted Apart
Google keeps most of its code in a single repository. According to a 2016 paper by two Google engineers, that repository held roughly two billion lines of code in about nine million unique source files, 86 TB of data, and around 35 million commits. In 2014 alone, about 15 million lines of code changed every week, which is roughly the size of the entire Linux kernel being rewritten weekly. Google spent years looking for an off-the-shelf version control system that could handle this scale in one repository, couldn't find one, and built its own, called Piper.
Now look at the opposite experience. By 2018, the team at Segment had split its server-side integrations into over 140 services, each living in its own repository. They wrote shared libraries to avoid duplicating work, but updating a shared library meant testing and deploying dozens of services, so engineers under time pressure updated it in just one place. Over time, every service ended up on a different version of the same libraries. After the team merged everything into one repository and committed to a single version of each of its 120 unique dependencies, they reported 32 improvements to the shared libraries while the old setup was in place, and 46 a year after the move.
These two stories are about the same question from opposite ends: where your code lives decides how easily it can change. That's the monorepo versus polyrepo debate. A monorepo keeps many projects in one repository. A polyrepo gives each project its own. Neither is automatically better, and plenty of successful companies use both. This guide explains what each really costs and buys you, and how to choose without copying Google.
If Git and project structure are still new to you, our Backend Engineer and System Design courses cover the fundamentals this comparison builds on.
This post originally appeared on the Ciphemic Academia blog.
The Short Version
- Monorepo: many projects, apps, and shared libraries live in one repository, usually with tooling that builds and tests only what a change affects.
- Polyrepo: each project, service, or library has its own repository, and shared code is published and consumed as versioned packages.
If you want one default: a small team with closely related projects usually does well in a monorepo with proper tooling, while independent teams with different release cycles, stacks, or access rules often do better with separate repositories. Don't copy Google's setup unless you have Google's problems, and tooling budget.
First, Don't Confuse a Monorepo With a Monolith
These two ideas get mixed up constantly, and they're independent:
- A monorepo is about where code is stored: one repository.
- A monolith is about how code is deployed: one application, shipped as one unit.
Google's monorepo holds a huge number of separately deployed services. You can run microservices out of one repository, and you can keep a monolith split across several repositories. Our monolith vs. microservices guide covers the deployment side of that choice. Segment's story involved both decisions at once, which matters when reading their results.
What Both Are Actually Trying to Solve
Before the differences, the shared problems:
- Sharing code safely, so one fix or improvement reaches everything that needs it
- Managing dependencies, so the pieces of your system agree on which versions of shared code they use
- Changing things across projects, such as renaming an API that ten services call
- Keeping builds and tests fast as the amount of code grows
- Setting clear ownership and access rules, so the right people can change the right code
Each structure gives different answers to these, and each answer comes with a price.
Monorepo
What it actually is: one repository containing multiple applications and libraries, with shared code imported directly from the same tree. Because everything is in one place, a single commit can change a library and every project that uses it. Mature monorepos rely on tooling that understands which projects depend on which, so CI can build and test only what a change affects.
A small example (a typical JavaScript monorepo layout and an affected-only command):
/repo /apps /web → customer website /admin → admin dashboard /api → backend service /packages /ui → shared components /auth → shared login logic package.json → one set of tooling and dependencies # Build and test only what a change actually affects (Nx example) npx nx affected -t build test Where it shines:
- Atomic cross-project changes: one pull request can update a shared library and all its callers together, so nothing is left broken in between. Google runs automated large-scale cleanups across its codebase this way
- One version of each dependency: the drift that hurt Segment can't happen when everything uses the same version by construction
- Easy code sharing and discovery: engineers can find and reuse code instead of rewriting it, and the directory structure itself acts as a map
- Consistent tooling: one set of lint rules, build scripts, and CI conventions
- Flexible ownership: teams can move between projects and work across boundaries without cloning a new repo
Where it struggles:
- It needs real tooling: Google built Piper and its own cloud-based workspace system, and smaller teams rely on build tools such as Bazel, Nx, or Turborepo to cache work and test only affected projects. Without them, CI time grows with the whole codebase
- Complexity and health costs: Google's own paper lists codebase complexity, unnecessary dependencies, and difficulty discovering code, along with the effort invested in code health, as costs of the approach
- Coarser access control: by default, everyone with access sees everything, so you need ownership rules such as CODEOWNERS and sometimes extra tooling for sensitive code
- A bad change has a wide blast radius: upgrading a dependency can break several projects at once, which Segment named as a trade-off even after choosing a monorepo
Who this suits: small and mid-sized teams with closely related projects, teams sharing a lot of code, and organizations that value atomic changes and consistent tooling enough to invest in the tooling to match.
Polyrepo
What it actually is: a separate repository for each project, service, or library. Shared code is published as a versioned package to a registry such as npm or PyPI, and each consumer chooses which version to depend on and when to upgrade.
A small example (separate repos, and a consumer pinning a shared package version):
github.com/acme/web → the website github.com/acme/api → the backend service github.com/acme/ui-components → published as an npm package github.com/acme/auth-lib → published as a versioned package // In web/package.json: this repo decides when to take new versions "dependencies": { "@acme/ui-components": "^2.4.0", "@acme/auth-lib": "^1.9.2" } Where it shines:
- Independent ownership and releases: a team can ship on its own schedule without waiting for anyone else's CI
- Clear boundaries and permissions: access can be granted per repository, which suits sensitive code, contractors, or open-source components
- Small, fast repositories: clones are quick, CI per repo is focused, and the tooling is just plain Git plus a pipeline
- Freedom to differ: each repo can use its own language, framework, and release process
- Limited blast radius: a broken change affects one project until someone chooses to adopt it
Where it struggles:
- Version drift: this is Segment's story. With no one forcing upgrades, projects fall behind, and eventually every service ran its own mix of library versions
- Cross-repo changes are slow and risky: changing a shared library means releasing it, then opening pull requests in every consuming repo, in the right order
- Duplicated setup: every repo needs its own CI config, lint rules, and conventions, which tend to diverge
- Harder discovery: useful code is scattered, so teams rebuild what already exists elsewhere
- Slow improvement of shared code: when each change to a shared library means touching dozens of repos, people avoid making improvements, which Segment described directly
Who this suits: organizations with many independent teams, different technology stacks, strict access boundaries, or products that genuinely release on separate cycles.
The Middle Ground
The choice isn't always pure. Google itself keeps Android and Chrome in separate Git repositories. Many companies keep related services in one repo and separate products in others. And polyrepo teams reduce drift with automation: tools like Renovate or Dependabot open upgrade pull requests automatically, and strict versioning conventions make upgrades predictable. The goal is the same either way: shared code should reach its consumers quickly and safely.
Side-by-Side Comparison
| Monorepo | Polyrepo | |
|---|---|---|
| Structure | Many projects in one repository | One repository per project or library |
| Shared code | Imported directly from the same tree | Published and consumed as versioned packages |
| Dependency versions | One version, enforced by structure | Each repo chooses, so drift is likely |
| Cross-project change | One atomic pull request | Coordinated pull requests across repos |
| CI model | Needs affected-only builds and caching to scale | Simple, per-repo pipelines |
| Tooling needed | Significant at scale (Bazel, Nx, Turborepo, or custom) | Minimal, plus dependency-update automation |
| Access control | Coarse by default, needs ownership rules | Fine-grained, per repository |
| Team independence | Lower, shared conventions | Higher, separate release cycles |
| Code discovery | Easy, one place to search | Harder, scattered across repos |
| Characteristic failure | Slow CI, tangled dependencies, wide blast radius | Version drift, slow cross-repo changes |
| Famous example | Google's single repository | Segment before it consolidated |
How This Fits a Career Path
- Full-stack and frontend engineers: JavaScript monorepos using workspaces and tools like Nx or Turborepo are common, so you'll likely work in one
- Backend and platform engineers: how code is organized shapes how services are built and released, and it's a regular architecture discussion
- DevOps and developer-experience roles: you'll design the CI that makes a monorepo fast or a polyrepo manageable; our GitHub Actions vs. Jenkins vs. CircleCI post compares the platforms involved, and our CI/CD Pipelines roadmap covers the fundamentals
- System design interviews: "how would you organize the code for twenty services?" is a common question, and a strong answer weighs shared code, release independence, tooling cost, and team structure
How to Choose Without Overthinking It
- Look at how often projects change together. If most changes touch several projects, a monorepo's atomic commits will save real time. If projects rarely touch each other, separate repos cost little.
- Decide how much you need one version of shared code. If drift would hurt, as it did for Segment, a monorepo enforces consistency for free. If you can automate upgrades, polyrepo can work too.
- Check your access and compliance needs. If some code must be hidden from some people, per-repo permissions are simpler.
- Be honest about tooling capacity. A monorepo without caching and affected-only CI gets slower every month. If you can't invest in that, keep repos smaller.
- Start with what your team size supports. Many small teams begin with a monorepo for simplicity and adopt build tooling when CI starts to slow. Large organizations with independent teams often start separate.
- Revisit it as you grow. Both Google's and Segment's setups changed as their needs did, so treat the structure as a decision you can revisit.
A note on honesty: Google's numbers come from a 2016 paper describing a company with a custom version control system and a large dedicated tooling team, which isn't a setup most organizations can or should copy. Segment's results are from its own report of one migration, and it merged its services into a single deployable at the same time as merging its repositories, so the improvement can't be credited to repository structure alone. The counts of "improvements" are one team's own measure. Treat both as illustrations of the trade-offs, not as benchmarks for your project.
Common Mistakes When Choosing a Repository Structure
- Confusing monorepo with monolith. One repo doesn't mean one deployable, and the reverse is also true.
- Adopting a monorepo without build caching or affected-only CI. Every pull request then rebuilds everything, and the pain grows with every project you add.
- Running a polyrepo with no automated dependency updates. Without them, drift is a matter of time.
- No ownership rules. In a monorepo, define who owns which directories and who must review changes there.
- A giant shared "utils" package. It becomes a dependency of everything and a bottleneck for every change.
- Copying Google at five engineers. Their solution answers their scale, and you'd inherit the complexity without the problem.
- Splitting repos for the wrong reason. Segment initially split to isolate failing tests, then found it was a false advantage because nobody felt pressure to fix them.
- Ignoring secrets and compliance. Make sure your structure supports the access rules you actually have.
Frequently Asked Questions
Is a monorepo the same as a monolith?
No. A monorepo is one repository, and a monolith is one deployable application. A monorepo can contain many independently deployed services.
Do I need Bazel to use a monorepo?
No. Bazel is one option, originally derived from Google's internal tooling. Smaller teams often use workspace features in their package manager along with tools like Nx or Turborepo.
Which is better for a startup?
Many startups start with a monorepo because it keeps sharing and changes simple while the team is small. The right answer depends on your stack, your release needs, and your tooling capacity.
Can a monorepo hold different languages?
Yes, though tooling gets more involved. Google's repository spans many languages, and build systems such as Bazel are designed for that.
How do permissions work in a monorepo?
By default, access is repository-wide, so teams use ownership files like CODEOWNERS for review rules and sometimes additional tooling for restricted directories. If you need strict separation, separate repositories are simpler.
How do interviewers evaluate this topic?
Usually by asking you to organize code for a hypothetical company. A strong answer weighs how often projects change together, how much shared code exists, who needs independent releases, and whether the team can support the tooling, instead of just naming a favorite.
Choose Where Your Code Lives on Purpose
Google's two billion lines in one place and Segment's 140 drifting services both show the same thing: repository structure shapes how fast and how safely a team can change its code. Pick based on how your projects relate and how your team works, then invest in the tooling that makes your choice sustainable. Explore the Backend Engineer and System Design courses to practice organizing a real multi-project codebase.
