Monorepo vs Polyrepo Architecture
Choose the right repository strategy. Covers monorepo tooling (Nx, Turborepo, Bazel), polyrepo coordination, code ownership, dependency management, and CI/CD for each approach.
The monorepo-vs-polyrepo debate isn’t about which is “better” — it’s about which trade-offs match your organization’s structure, team size, and deployment model. Google runs a monorepo with 2 billion lines of code. Amazon runs thousands of polyrepos. Both work at massive scale because they chose the patterns that fit their organizational culture.
Decision Framework
| Factor | Monorepo | Polyrepo |
|---|---|---|
| Team size | Small-medium (< 100 engineers) | Large distributed teams |
| Shared code | Lots of shared libraries | Minimal sharing between services |
| Deployment | Coordinated releases common | Independent deploy per service |
| Tooling investment | Need custom build tooling | Standard Git workflows work |
| Code visibility | Everyone sees everything | Team owns their repo |
| Dependency management | Single version of everything | Version per service |
Monorepo Structure
company-monorepo/
├── apps/
│ ├── web-app/
│ │ ├── src/
│ │ ├── package.json # Depends on @company/ui, @company/api-client
│ │ └── tsconfig.json
│ ├── api-server/
│ │ ├── src/
│ │ └── package.json # Depends on @company/shared-types
│ └── mobile-app/
│
├── packages/
│ ├── ui/ # Shared UI component library
│ │ ├── src/
│ │ └── package.json
│ ├── shared-types/ # TypeScript types shared across apps
│ └── api-client/ # Generated API client
│
├── tools/
│ └── scripts/ # Build, deploy, CI scripts
│
├── turbo.json # Turborepo pipeline config
├── package.json # Root workspace config
└── .github/
└── workflows/
└── ci.yml # Affected-only CI
Monorepo CI: Build Only What Changed
# turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
}
}
}
# Only build/test affected packages
npx turbo run build test --filter=...[HEAD~1]
# Result: if only `packages/ui` changed,
# builds: packages/ui, apps/web-app (depends on ui)
# skips: api-server, mobile-app (unaffected)
Code Ownership
# CODEOWNERS file
# Each team owns their directories
/apps/web-app/ @frontend-team
/apps/api-server/ @backend-team
/apps/mobile-app/ @mobile-team
/packages/ui/ @design-system-team
/packages/shared-types/ @platform-team
# Cross-cutting concerns need multi-team review
/packages/api-client/ @frontend-team @backend-team
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Monorepo without build tooling | CI takes 45 min (builds everything) | Turborepo, Nx, or Bazel for affected-only builds |
| Polyrepo with tight coupling | Services depend on each other, breaking constantly | Contract tests, or consider monorepo |
| No code ownership | Everyone changes everything, no accountability | CODEOWNERS file, team-owned directories |
| ”We’ll split later” | Monolith in a monorepo, not actual modularity | Enforce module boundaries, no circular dependencies |
| Too many repos (per microservice) | Coordination overhead, dependency management nightmare | Group related services in repos |
Checklist
- Repo strategy chosen based on team/org structure
- If monorepo: build tool configured (Turborepo/Nx/Bazel)
- If monorepo: affected-only CI to keep builds fast
- CODEOWNERS defined per team/directory
- Dependency management: workspace protocol or published packages
- CI pipeline: lint → build → test (affected only)
- Module boundaries: no circular dependencies between packages
- If polyrepo: contract tests between services
:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For engineering consulting, visit garnetgrid.com. :::