👋Hi, I'm Waqas — a Software Architect and Technical Consultant specializing in .NET, Azure, microservices, and API-first system design..
I help companies build reliable, maintainable, and high-performance backend platforms that scale.
Monorepo vs Polyrepo: Trade-offs for .NET and Microservices
Single repo vs many: build speed, ownership, refactoring. When monorepo pays off.
May 12, 2025 · Waqas Ahmad
Read the article
Introduction
This guidance is relevant when the topic of this article applies to your system or design choices; it breaks down when constraints or context differ. I’ve applied it in real projects and refined the takeaways over time (as of 2026).
Choosing how to organise code—monorepo (one repo, many projects) or polyrepo (one repo per service)—affects collaboration, build speed, cross-service refactors, and CI/CD. This article explains what each is, when to use which, tooling that keeps monorepos fast (path filters, affected builds), and how to scale without chaos. For architects and tech leads, the decision matters when you need atomic refactors and shared code (monorepo) or independent releases and clear ownership (polyrepo).
System scale: One product (many projects) to many products; from a single team to many teams. Applies when you’re deciding how to organise repos and shared code.
Team size: One to many teams; someone must own repo structure, CI (path filters or per-repo), and shared package versioning if polyrepo. Works when the team can agree on ownership and release boundaries.
Time / budget pressure: Fits greenfield and “we refactor across services often”; breaks down when there’s no tooling for monorepo (affected builds, path filters)—then polyrepo or hybrid is safer.
Technical constraints: Git; .NET (solutions, projects) and/or front-end (Nx, Turborepo); CI that supports path filters or per-repo builds. Assumes you can invest in build and permission tooling if monorepo.
Non-goals: This article does not optimise for a single tiny repo; it focuses on monorepo vs polyrepo (and hybrid) for multi-project or multi-service setups.
What is a monorepo?
A monorepo (single repository) is one version-controlled repository that holds multiple projects, apps, or services—often the whole product or a large part of it. For example, one repo might contain: a .NET Web API, several Azure Functions, a shared class library, a Vue or React frontend, and maybe mobile apps or scripts. Everyone on the team clones the same repo; the entire codebase lives in one place with one history.
Why use one?Atomicity and shared code. Rename an API or change a shared contract in one commit and one PR: API, clients, and tests update together. No “release library v2.3 then update every consumer repo”—change the library and every project that references it sees the change immediately. Refactoring and cross-service changes get simpler. For .NET, a monorepo is often one solution (.sln) with many projects: MyCompany.Api, MyCompany.Functions, MyCompany.Contracts, MyCompany.Frontend. Build and CI run against the same repo; use path filters or affected build (Nx, Turborepo, Azure Pipelines) so only changed projects build.
Downsides. Repo size (long clone, slow git status). Permissions are coarse unless you add CODEOWNERS, branch protection per path, or similar. Builds can be slow without incremental/affected tooling; merge conflicts can rise with many people in one repo—small PRs and good branching help.
What is a polyrepo?
Polyrepo means each service, app, or product has its own repo: e.g. orders-api, billing-service, frontend, and shared code as NuGet or npm packages. Teams own their repo; CI/CD runs per repo—push to orders-api builds only the orders API.
Why use it?Clear ownership and independent release cycles. Team A owns orders-api, Team B billing-service; they release on different schedules and can use different stacks. Clones and builds are smaller and faster. Permissions are natural: repo access = team access. Fits many teams and loosely coupled domains.
Downsides. Cross-repo refactors are painful: release a new package version, then PRs in every consumer repo to bump and adapt. Shared code needs NuGet/npm (or internal feeds) and versioning. Discoverability: new joiners hunt across repos; docs and ADRs can be scattered.
Monorepo vs polyrepo: side-by-side
Aspect
Monorepo
Polyrepo
Repos
One repo, many projects
One repo per service/app
Refactors
Atomic across codebase
Multiple PRs, version bumps
Shared code
Same repo, project references
Packages (NuGet, npm), versioning
CI/CD
One pipeline or path-filtered builds
One pipeline per repo
Ownership
Often coarse (whole repo)
Clear per repo
Clone size
Large (whole product)
Small (one service)
Permissions
Tooling needed for fine-grained
Natural (repo = boundary)
Best fits
Small/medium teams, heavy shared code
Many teams, independent domains
When to choose a monorepo
Choose a monorepo when:
You have a small or medium number of services (e.g. one product: API, frontend, a few functions) and shared libraries or contracts change often. One repo keeps everything in sync and makes refactors atomic.
You want a single source of truth: one place to run tests, lint, and see the full dependency graph. Tools like Nx or Turborepo can run only affected projects, so CI stays fast.
Your team is one or a few squads that work across the stack. You do not need strict “team A never sees team B’s code” boundaries.
You are greenfield or consolidating: starting one repo is simpler than managing many repos and package versions from day one.
Example structure (monorepo):
MyProduct/
src/
Api/ # ASP.NET Core Web API
Functions/ # Azure Functions
Contracts/ # Shared DTOs, interfaces
Frontend/ # Vue or React app
tests/
Api.Tests/
Contracts.Tests/
.github/workflows/ # or Azure Pipelines; path filters for affected
Tooling: Use Nx (with nx-dotnet or custom), Turborepo, or Azure Pipelines with path filters (e.g. only build src/Api when src/Api or src/Contracts change). This keeps build and test time under control.
When to choose a polyrepo
Choose polyrepo when:
You have many teams with clear ownership (e.g. team per service). Each team wants its own repo, release cadence, and possibly tech stack.
Domains are loosely coupled: orders and billing do not share much code; they communicate via APIs and events. Shared code is minimal or published as versioned packages.
You need strict permissions: only the billing team sees the billing repo. Monorepo would require extra tooling (e.g. CODEOWNERS, branch rules per path) to get the same effect.
You are integrating with external or partner teams that contribute to specific repos; polyrepo gives a clear boundary per repo.
Example:orders-api repo (Team A), billing-service repo (Team B), shared-contracts repo published to NuGet; each repo has its own pipeline and release cycle.
Hybrid: monorepo for one product, polyrepo across products
A common hybrid is: one monorepo per product (e.g. “Customer Portal” = API + frontend + shared libs in one repo), and separate repos for other products or shared platforms. That way you get atomic refactors and simple shared code within the product, and clear boundaries between products or teams. For .NET and microservices, this often means: monorepo for the “main” app and its services; polyrepo for internal tools, separate business units, or open-source libraries.
Tooling for monorepos (.NET and frontend)
Nx: Affected builds and caching; plugins for .NET and Node. Run only what changed: nx affected:build.
Turborepo: Same idea for JavaScript/TypeScript; can sit alongside .NET in the same repo.
Azure Pipelines: Use paths in the trigger so the pipeline runs only when certain folders change. Example: paths: include: ['src/Api/**', 'src/Contracts/**'] so the API pipeline runs only when those change.
Git sparse checkout: Clone only part of the repo to cut size (advanced).
Enterprise best practices
Decide per product or domain. One monorepo per product (e.g. Customer Portal = API + frontend + shared libs) and polyrepo for other products or shared platforms is a common enterprise pattern. Avoid “one monorepo for the whole company” unless you have Google-scale tooling and culture.
Permissions in a monorepo. Use CODEOWNERS and branch protection so only the right people can merge to sensitive paths. Document who owns what; without it, everyone with repo access sees everything.
CI/CD. In monorepos use path filters or affected builds so every push does not build the whole solution. In polyrepo, one pipeline per repo; shared packages go to an internal NuGet/npm feed with a clear versioning and release process.
Onboarding. In a monorepo, point new joiners at one repo and a single README or doc that describes the layout. In polyrepo, maintain a repo index (list of repos, owners, and what each contains) so people know where to look.
Refactors. In monorepo, do cross-service renames and contract changes in one PR. In polyrepo, plan a version bump of the shared package and coordinate PRs in consumer repos; use a runbook so teams know the steps.
Common issues and challenges
Monorepo too big. Clone and git status slow; CI runs forever. Fix: affected/incremental builds (Nx, Turborepo, path filters), sparse checkout if needed, and trim dead code.
Polyrepo refactor hell. Changing a shared API means many PRs and version bumps. Fix: version shared packages clearly (semver), document the “bump and consume” process, and consider a monorepo for the set of services that change together often.
Permissions. Monorepo: everyone sees everything unless you add CODEOWNERS and branch rules. Polyrepo: natural per-repo access but more repos to manage. Choose the model that matches how you want to segment access.
Merge conflicts. More people in one repo = more conflicts. Keep PRs small, branch from main often, and use short-lived feature branches. If it becomes unmanageable, consider splitting into a few repos (hybrid) rather than one giant monorepo.
Monorepo (one repo, many projects) suits atomic refactors, shared code, and one pipeline with path filters; polyrepo (one repo per service) suits many teams, independent releases, and clear ownership—in enterprise, hybrid (one monorepo per product, polyrepo across products) is common. Choosing monorepo without affected builds or path filters slows CI; choosing polyrepo when you refactor across services often adds coordination cost. Next, decide by product and team boundaries, then add path filters or affected-build tooling if you go monorepo; use the FAQs below for quick reference.
Position & Rationale
I use monorepo when we have a small or medium set of services and shared code that changes together often—one product, one or a few teams, and we want atomic refactors and a single source of truth. I use polyrepo when teams own distinct products or services and need independent release cycles and clear repo-level ownership. I prefer hybrid (one monorepo per product, polyrepo across products) when the organisation has multiple products but each product is a coherent set of services and front ends. I avoid monorepo without affected build or path filters—otherwise every push builds everything and CI slows down. I don’t choose monorepo “because Google does it” if we don’t have the tooling or discipline for large-repo workflow.
Trade-Offs & Failure Modes
Monorepo sacrifices per-repo isolation and fine-grained permissions; you gain atomic changes and shared code in one place. Polyrepo sacrifices cross-repo refactors and forces package versioning for shared code; you gain independent releases and clear ownership. Hybrid adds two models to explain but can match “one product = one repo, many products = many repos.” Failure modes: monorepo without path filters (slow CI); polyrepo with shared libs that change often (version hell); merge conflicts in monorepo when PRs are large and long-lived.
What Most Guides Miss
Most guides compare pros/cons but don’t stress that affected build (or path filters) is non-negotiable for monorepo—without it, CI becomes the bottleneck. Another gap: hybrid is underplayed; many enterprises end up with “monorepo for product A, polyrepo for the rest” and that’s a valid outcome. Permissions in monorepo (CODEOWNERS, branch protection per path) are often omitted; without them everyone can change everything.
Decision Framework
If one product, shared code changes often, one or a few teams → Monorepo; use path filters or affected build in CI.
If many teams, independent releases, clear per-service ownership → Polyrepo; version shared packages (NuGet, npm).
If multiple products, each with many services → Hybrid: one monorepo per product, polyrepo across products.
For monorepo → Invest in incremental/affected builds and permissions (CODEOWNERS); keep PRs small.
For polyrepo → Document shared package versioning and “bump and consume”; consider monorepo for the subset that changes together.
Key Takeaways
Monorepo = atomic refactors, shared code; need path filters or affected build. Polyrepo = independent releases, clear ownership; need package versioning for shared code.
Hybrid (monorepo per product, polyrepo across products) is common in enterprise.
Don’t adopt monorepo without CI that only builds what changed.
Permissions and CODEOWNERS matter in monorepo; document release and versioning in polyrepo.
When I Would Use This Again — and When I Wouldn’t
I’d use monorepo again for a single product with multiple services and shared code that we refactor often, and when we have path filters or affected build in CI. I’d use polyrepo again when teams own distinct services and need independent releases. I’d use hybrid when the org has several products and each product is a natural monorepo. I wouldn’t choose monorepo without tooling for incremental builds—full rebuilds on every push don’t scale. I also wouldn’t force one model on the whole company if different products have different needs.
Frequently Asked Questions
Frequently Asked Questions
What is a monorepo?
A monorepo is a single version-controlled repository that contains multiple projects, apps, or services (e.g. API, frontend, shared libs). It enables atomic refactors and shared code in one place; it requires tooling for incremental or affected builds (e.g. Nx, Turborepo, path filters) and often for permissions (CODEOWNERS).
What is a polyrepo?
Polyrepo means each service or application has its own repository. It enables clear ownership and independent release cycles; cross-repo refactors and shared code require package versioning (NuGet, npm) and coordination.
When should I use a monorepo?
Use a monorepo when you have a small or medium number of services, shared code that changes often, and one or a few teams working across the stack. It pays off when you want atomic refactors and a single source of truth.
When should I use a polyrepo?
Use polyrepo when you have many teams with clear ownership, loosely coupled domains, and a need for independent release cycles or strict per-repo permissions.
How do I keep monorepo builds fast?
Use affected or incremental builds: only build and test what changed. Tooling: Nx (nx affected:build), Turborepo, or Azure Pipelines with paths filters. Avoid building the entire solution on every commit.
How do I share code in a polyrepo?
Publish shared code as packages (NuGet for .NET, npm for frontend) to a feed or registry. Consumer repos depend on package versions and bump them when needed. Use semantic versioning and a clear release process.
What is a hybrid monorepo/polyrepo?
Use one monorepo per product (e.g. API + frontend + shared libs in one repo) and separate repos for other products or teams. You get atomic refactors within the product and clear boundaries between products.
Does .NET work well in a monorepo?
Yes. One solution (.sln) with many projects; use project references for shared code. Combine with Nx, Turborepo, or Azure Pipelines path filters so only affected projects build. Standard .NET tooling works; you add CI logic for affected builds.
How do I do path filtering in Azure Pipelines for a monorepo?
Use paths in the trigger or in a condition so the pipeline runs only when certain folders change. Example: paths: include: ['src/Api/**', 'src/Contracts/**'] so the API pipeline runs only when those folders change.
What are Nx and Turborepo?
Nx and Turborepo add affected builds and caching to monorepos: they compute which projects depend on changed files and run only those. Nx has .NET support; Turborepo is popular for JavaScript/TypeScript.
How do I handle permissions in a monorepo?
Use CODEOWNERS, branch protection rules per path, or tooling (e.g. Nx boundaries) to restrict who can change which folders. Without this, everyone with repo access sees everything.
What if we already have many repos and want to consolidate?
Plan migration (move projects into one repo, fix paths, set up new CI), communicate with teams, and consider doing it incrementally (e.g. one product at a time). Keep history (e.g. git subtree / git filter-repo) if you need it.
Can I use both monorepo and polyrepo in the same company?
Yes. Many companies use a monorepo for one product and polyrepo for others (hybrid). Choose per product or team based on coupling and ownership.
What are enterprise best practices for monorepo vs polyrepo?
Decide per product or domain: one monorepo per product, polyrepo for other products or shared platforms. Use CODEOWNERS and path-based branch protection in monorepos. In polyrepo, maintain a repo index (list of repos, owners, what each contains) for onboarding. Use path filters or affected builds in CI so monorepo builds stay fast.
How do I onboard new joiners in a polyrepo?
Maintain a repo index (list of repos, owners, and what each repo contains) and point new joiners at it. Document the “bump and consume” process for shared packages so they know how to update dependencies across repos.
Related Guides & Resources
Explore the matching guide, related services, and more articles.