Codebase organisation comparison
Monorepo vs polyrepo
One repository for everything, or one repository per project? The answer drives how teams coordinate, how CI scales, and how easy cross-project changes feel.
TL;DR
- Monorepo — one Git repository contains many projects. Atomic cross-project commits, shared tooling, single version of truth — at the cost of CI infrastructure investment.
- Polyrepo — each project owns its repo. Smaller, focused, simpler — at the cost of dependency drift and harder cross-cutting changes.
- The right choice tracks team coupling. Tightly-coupled teams → monorepo. Loosely-coupled, independent teams → polyrepo.
Side-by-side comparison
| Aspect | Monorepo | Polyrepo |
|---|---|---|
| Repositories | One | Many |
| Cross-project change | One PR atomic across services | Multiple PRs coordinated; release ordering matters |
| Dependency versioning | One version of everything | Each repo pins its own versions — drift inevitable |
| Code sharing | Trivial — import directly | Via published packages with semver |
| CI scope | Must compute affected projects to stay fast | Naturally scoped per repo |
| Tooling | Nx, Turborepo, Bazel, Pants required at scale | Standard Git + per-repo CI is enough |
| Discovery | One search across everything | Per-repo search; cross-repo discovery is hard |
| Access control | Path-based or trust-based | Per-repo, GitHub/GitLab native |
| Cloning | Slow as repo grows (shallow / sparse helps) | Fast — only what you need |
| Used by | Google, Meta, Microsoft, Uber, X | Most open-source, most startups |
Layout side-by-side
Monorepo
acme/ ← single repo
apps/
web/
mobile/
admin/
services/
auth/
billing/
orders/
packages/
ui/ ← shared UI lib
config/
types/
package.json ← root workspace
turbo.json ← build orchestration
.github/workflows/ Polyrepo
acme/web ← separate repo
acme/mobile ← separate repo
acme/admin ← separate repo
acme/auth-service ← separate repo
acme/billing-service ← separate repo
acme/orders-service ← separate repo
acme/ui-library ← published as @acme/ui
acme/types ← published as @acme/types
# Each repo:
# package.json with deps on @acme/ui@^1.2
# its own CI, releases, README When to choose a monorepo
- Shared design system / types. Frontend, mobile-web, and admin tools sharing a UI library — atomic upgrades are huge.
- Tightly coupled services. Updating an API contract and all its consumers should be one reviewable change.
- Standardised tooling. One linter config, one CI template, one dependency upgrade strategy across many projects.
- Single-org refactoring. "Rename this function everywhere" should be one PR, not 23.
- You have engineers to invest in CI tooling. Nx/Turborepo/Bazel are not free to set up and maintain.
When to choose polyrepos
- Independent teams with independent products. No reason to couple their release cycles.
- Open-source projects. External contributors expect focused, well-scoped repos.
- Strong service boundaries already. No frequent cross-service changes — coordination overhead is rare.
- Limited platform team. No one to own monorepo tooling; standard GitHub workflow is fine.
- Compliance separation. Some code must be visible only to specific teams; per-repo ACLs are simpler than path-based.
English phrases engineers use
Monorepo conversations
- "This PR updates the shared types and all consumers in one change."
- "CI is rebuilding everything — we need affected-project detection."
- "Codeowners for the auth package — review required from the auth team."
- "Use workspaces for the package linking, not symlinks."
- "Sparse checkout — junior devs don't need the entire codebase."
Polyrepo conversations
- "We need to release the library first, then bump it in the consumer."
- "This is dependency drift — three services on three different SDK versions."
- "The change spans four PRs across four repos — let's coordinate the merge order."
- "Renovate bot is keeping all repos on the latest minor."
- "Open a cross-repo issue — link them so we don't lose track."
Quick decision tree
- Frontend + design system shared across products → Monorepo
- Microservices with frequent cross-service API changes → Monorepo (with tooling)
- Open-source library → Polyrepo
- Small team, single product, < 10 engineers → Either works; default monorepo for simplicity
- Independent product teams, weekly cross-team contact → Polyrepo
- Compliance-isolated codebase (PCI, classified) → Polyrepo
- No platform team to maintain tooling → Polyrepo
- You said "monorepo" and your team groaned → Polyrepo — culture matters
Common Mistakes and Trade-offs
A surprisingly common mistake with monorepos is treating them as simply 'bigger' polyrepos. Teams often assume that adding more projects to a single repository automatically improves collaboration or reduces duplication, but this frequently backfires. The increased size introduces significant tooling complexity – build times become crippling, IDE performance degrades dramatically, and dependency management becomes a nightmare without robust infrastructure. This isn't about the number of projects; it's about the interconnectedness and scale. Teams need to invest heavily in dedicated CI/CD pipelines, sophisticated caching strategies, and potentially even separate build environments tailored for specific project types to avoid a catastrophic slowdown. Ignoring this fundamental shift in operational burden is where many monorepo initiatives fail.
At production scale – say, beyond 50-100 projects – the performance bottleneck of a single repository becomes acutely apparent. While polyrepos allow independent deployments and rollbacks, a monorepo's build system can severely restrict release velocity. The cumulative impact of even minor changes across multiple dependencies creates significant contention for resources. This isn't just about IDE sluggishness; it directly translates into increased deployment times, slower feedback loops, and ultimately, reduced development productivity. Polyrepos, despite their initial simplicity, often provide a more predictable and manageable production environment as the codebase grows, allowing for granular control over deployments that are simply impossible with a single, massive repository.
There's a persistent misconception that monorepos always lead to better code sharing and reduced duplication. This assumes a proactive culture of shared components and disciplined reuse—which is fantastic if it exists, but rarely the default. In reality, a monorepo can easily become a dumping ground for orphaned projects, leading to increased complexity and difficulty in understanding dependencies. Teams often end up duplicating logic simply because they don't have a clear, enforced process for sharing code or because changes across different parts of the repository are too risky to integrate directly. Polyrepos force you to explicitly define interfaces and APIs, encouraging genuine reuse rather than implicit dependency on shared code.
Migration between monorepos and polyrepos isn't a straightforward copy-paste operation; it's fundamentally a reorganization of workflows and tooling. Attempting to simply split a large monorepo into smaller repositories without addressing the underlying architectural issues – like inconsistent build processes or lack of dependency management – will just create a collection of smaller, equally problematic polyrepos. A more effective strategy often involves initially adopting a monorepo with carefully designed boundaries and then strategically introducing polyrepos around specific, tightly coupled domains as complexity grows. Furthermore, hybrid approaches—where some core libraries are shared within a monorepo while others reside in separate repositories—can offer a pragmatic balance, but require meticulous governance to avoid creating a tangled web of dependencies.
Frequently asked questions
What is a monorepo?
A monorepo is a single version-control repository containing the code for many projects, services, or libraries — owned by the same organisation. Google, Meta, and Microsoft famously run multi-billion-line monorepos; smaller teams use them for related microservices, design systems, or shared tooling.
What is a polyrepo?
A polyrepo (or "multi-repo") setup gives each project its own version-control repository. This is the default model on GitHub and the historical norm — each service, library, or product lives in a separate repo with its own issues, releases, and access control.
Are monorepos faster to work in?
For atomic cross-project changes, yes — you can update an API and all its consumers in one PR. For everyday work in a single project, polyrepos are usually faster (smaller repo to clone, faster CI, less context to learn).
Do I need special tooling for a monorepo?
For more than a handful of projects, yes. Build systems like Nx, Turborepo, Bazel, or Pants compute affected projects per PR so CI does not rebuild the world on every commit. Without that tooling, monorepo CI becomes extremely slow.
What is a "trunk-based development" monorepo?
A monorepo where everyone commits to a single main branch (no long-lived feature branches), backed by feature flags and CI gates. This is the dominant pattern at Google and similar large companies — it scales well when paired with strong test culture and code review.
Can I have a hybrid?
Yes — many teams keep tightly-coupled code (e.g. frontend monorepo with web, mobile-web, and design system) in one repo, while keeping unrelated backend services in their own repos. The grouping should follow team boundaries and change-frequency, not arbitrary technical lines.
What is the most underrated cost of polyrepos?
Dependency drift. The shared library is v1.4 in repo A, v1.7 in repo B, v0.9 in repo C — and a security patch must be released to all of them. Monorepos make "one version of everything" trivial; polyrepos make it a perpetual project.