Suggest an editImprove this articleRefine the answer for “Monorepo vs Polyrepo - pros and cons?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Monorepo vs polyrepo**: monorepo keeps all projects in one Git repo; polyrepo gives each project its own. Monorepo enables atomic commits across apps and shared libraries with no version drift. Polyrepo suits independent teams with separate deploy cadences. **Key point:** shared code pulls toward monorepo, team autonomy pulls toward polyrepo.Shown above the full answer for quick recall.Answer (EN)Image**Monorepo vs polyrepo** - monorepo keeps all projects in one Git repository; polyrepo gives each project its own repo. ## Theory ### TL;DR - Monorepo is one shared kitchen (same ingredients, same tools); polyrepo is separate kitchens per chef, with every spice duplicated - Core difference: monorepo lets you ship a change to `shared-ui` and update all consuming apps in one commit; polyrepo needs a publish cycle plus version bumps in each consumer - More than 5 interdependent packages, lean toward monorepo; independent teams owning separate services, lean toward polyrepo - Main tools: Nx, Turborepo, Bazel for monorepo; Lerna and git submodules for polyrepo coordination - Real numbers: Google runs one Bazel monorepo at 86TB; Netflix runs 700+ separate repos via Spinnaker ### Quick example Monorepo structure (Nx workspace): ``` my-company/ apps/ web/ # React app - imports shared-ui directly api/ # Node.js backend libs/ shared-ui/ # UI components, no publish step needed nx.json # Builds only changed projects ``` Polyrepo equivalent: ``` my-company-web/ # Git repo #1 - imports shared-ui via npm my-company-api/ # Git repo #2 my-company-ui/ # Git repo #3 - published to npm registry ``` In the monorepo, one `git commit` updates `web` and `shared-ui` together. In polyrepo, that same change needs three commits, an npm publish, and version bumps in every consumer. ### Key difference The real gap shows up when you change a shared library. In monorepo, you update `shared-ui`, and the dependency graph (Nx computes it from `tsconfig.json` paths) automatically flags `web` for rebuild. One PR, one review, zero version mismatch. In polyrepo, the same change requires publishing a new npm version, updating the version pin in every consumer, waiting for CI to pass in each repo separately, and coordinating merges across teams. Version drift is not theoretical - it happens on every non-trivial shared library update. ### When to use **Choose monorepo when:** - Multiple apps share UI components, utilities, or types - You need atomic changes across the stack (update an API contract and the frontend in one PR) - Team is small to medium (under 50 developers) and owns the full product - You are building a design system used by 3+ apps **Choose polyrepo when:** - Teams own fully independent services with no shared code - You need strict access control at the repo level (compliance, security isolation) - Services deploy on completely separate cadences with no coupling - You are running [microservices](/questions/microservices-architecture) where each service is a black box ### Comparison table | Aspect | Monorepo | Polyrepo | |---|---|---| | Code sharing | Direct imports, no publishing | npm/package publish + version pinning | | Commits | Atomic across all projects | Separate PRs, sync via tags/releases | | Build speed | Cached/incremental (Turborepo caches 90%+) | Full rebuild per repo | | Repo size | Grows to 100GB+ | Small (1-5GB each) | | Access control | Path-based via CODEOWNERS | Repo-level permissions | | CI/CD | Complex orchestration (Nx affected) | Simple per-repo pipelines | | Tooling | Nx, Turborepo, Bazel | Lerna, git submodules | | Who uses it | Google, Meta, Microsoft | Netflix, AWS | | When to use | Shared code, unified product | Team autonomy, independent services | ### How Git and build tools handle this Git in monorepo treats the entire repository as one unit. A `git diff` spans all projects. Tools like Nx parse `project.json` and `tsconfig.paths` to build a dependency graph, then run `nx affected:build` to rebuild only what changed. Touch `shared-ui`, and Nx traces which apps import it and triggers only those builds. At Google scale, Bazel takes this further: it hashes every input (source file plus deps) and checks a remote cache first. With a 95% cache hit rate, building 100,000 files takes roughly the same time as building 1,000. Polyrepo has no cross-repo awareness by default. CI runs independently per repo. When `shared-ui` publishes v2.1.0, nothing automatically notifies consuming repos. Teams discover breaking changes only when their own CI breaks after updating the version pin. ### Common mistakes **Running full CI on every push in monorepo** Without affected filtering, a monorepo build runs all projects on every push. With 10 apps, that is a 10-minute [CI pipeline](/questions/ci-cd-pipelines) for a one-line fix. ```bash # Wrong: rebuilds everything npm run build:all # Right: rebuilds only affected projects and their dependents npx nx affected:build --base=main ``` **Assuming monorepo has no access control** This is wrong. GitHub and GitLab both support path-based branch protection. A CODEOWNERS file gives granular review requirements per directory: ``` # .github/CODEOWNERS /libs/shared-ui/ @ui-team /apps/api/ @backend-team /infra/ @platform-team ``` Pull requests touching `libs/shared-ui` automatically request review from `@ui-team`. The access model is more granular than polyrepo's repo-level permissions, not less. **Polyrepo without automated versioning** Manual version bumps cause broken deploys. If `shared-ui` publishes a major version but a downstream team misses the bump, their build breaks without warning. Fix: automate with conventional commits plus `npm version` and changelog generation in CI. **Migrating to monorepo by merging git histories** Merging full histories from 5 repos creates an unreadable git log with 50,000 commits from unrelated projects. Use `git filter-repo` to squash history, or import with a clean initial commit per project. **Ignoring remote caching** Monorepo without remote cache slows down as the codebase grows and eventually hits polyrepo speed. Turborepo's Vercel Remote Cache or Nx Cloud distribute cached artifacts across all developers and CI machines. One engineer's build populates the cache for the next ten. ### Real-world usage - **Google** uses Bazel on a single monorepo with 2 billion lines of code and 120,000 engineers; changes to Android and Chrome ship in one atomic commit - **Meta** runs Buck on a monorepo containing React Native and all web apps - **Microsoft** uses Nx for VS Code and TypeScript development - **Netflix** operates 700+ separate repos managed via Spinnaker for deployment orchestration - **Uber** uses a monorepo with 1,000+ packages for coordinating mobile and web platforms ### Follow-up questions **Q:** How does Nx determine which projects are affected by a change? **A:** Nx parses `project.json` and `tsconfig.paths` to build a dependency graph. Running `nx graph` visualizes it. When you push, Nx compares the current commit to a base SHA and traces which projects import the changed files. **Q:** What is the tradeoff between Bazel and Turborepo? **A:** Bazel is hermetic: builds are reproducible across any machine because every input is hashed and isolated. Turborepo is much faster to set up and works well for JavaScript-only projects. Bazel makes sense at 100,000+ files; Turborepo covers most teams below that threshold. **Q:** How do you enforce module boundaries in a monorepo? **A:** Nx provides `enforceModuleBoundaries` in the eslint config. It blocks invalid imports between packages based on tags you assign to each project. For example, `web` can import `shared-ui` but not `api`. **Q:** Can polyrepo teams share code without switching to monorepo? **A:** Yes, via a private npm registry. Teams publish packages and consume them as versioned dependencies. The tradeoff is the publish-update-merge cycle on every shared change. Git submodules are an option but slow to update and prone to state inconsistencies. **Q:** How do you scale a monorepo to thousands of developers? **A:** Google answers this with Piper, an internal VCS handling 1 million commits per day, path-based ACLs, and Bazel remote execution (RBE) which distributes build steps across a cluster. For smaller scale, Nx Cloud or Turborepo remote caching cover the main bottleneck. ## Examples ### Sharing a utility with Turborepo ```json // apps/web/package.json { "dependencies": { "@my-company/shared-utils": "workspace:*" } } ``` ```ts // apps/web/src/api.ts import { formatDate } from '@my-company/shared-utils'; const display = formatDate(new Date()); // "2024-01-15T00:00:00.000Z" ``` ```ts // packages/shared-utils/src/date.ts export const formatDate = (d: Date): string => d.toISOString(); ``` When `formatDate` changes, Turborepo detects the change, skips `apps/api` (unaffected), and rebuilds only `apps/web`. The cache hit for `api` saves 40-60 seconds on a typical build. No npm publish, no version bump, no coordination. ### Polyrepo coordination with semantic versioning That same `formatDate` change in polyrepo looks like this: ```bash # In my-company-ui repo git commit -m "fix: formatDate returns ISO string" npm version patch # bumps to 1.0.1 npm publish # pushes to npm registry # In my-company-web repo (separate PR, separate CI run) npm install @my-company/shared-utils@1.0.1 git commit -m "chore: bump shared-utils to 1.0.1" ``` Two repos, two PRs, two CI runs. If `web` and `mobile` both consume `shared-utils`, that is three coordinated changes for one bug fix. This coordination cost grows linearly with the number of consumers. ### Access control in monorepo with CODEOWNERS ``` # .github/CODEOWNERS # UI team reviews all shared component changes /libs/shared-ui/ @ui-team # Backend team reviews API changes /apps/api/ @backend-team # Platform team reviews infrastructure /infra/ @platform-team ``` A PR touching `libs/shared-ui` automatically requests review from `@ui-team`. Nobody outside that team can merge those changes unreviewed. The model is more fine-grained than polyrepo's repo-level permissions. The concern that "monorepo means no access control" comes from teams that never configure CODEOWNERS.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.