The repository strategy is not really a Git decision. It is an architecture, ownership, and team-boundary decision.
When a team starts building multiple services, one question usually appears sooner or later:
Should we keep everything in a monorepo, or should every service have its own repository?
The discussion often turns into a familiar list:
- Monorepo = easier collaboration
- Polyrepo = better isolation
- Monorepo = easier refactoring
- Polyrepo = better team autonomy
All of these can be true.
And yet, they don't answer the question that actually matters:
Which repository structure will make our engineers more effective as the system and organisation grow?
After working with distributed systems, microservices, shared libraries, CI/CD pipelines and multiple engineering teams, I've found that the repository itself is rarely the root problem.
The more important questions are:
- Who owns the code?
- How often do services change together?
- How tightly coupled are the teams?
- How are dependencies managed?
- How expensive is CI/CD?
- How easy is cross-service refactoring?
- Where do security and access boundaries exist?
- How much autonomy do teams actually need?
This article looks at monorepo vs polyrepo from that perspective.
1. First: What Are We Actually Deciding?
At first glance, the decision seems simple.
Monorepo
company/
├── services/
│ ├── orders/
│ ├── payments/
│ ├── customers/
│ └── notifications/
├── libraries/
│ ├── auth/
│ ├── logging/
│ └── common/
├── frontend/
└── infrastructure/
One repository contains multiple applications, services and shared components.
Polyrepo
orders-service → repository
payments-service → repository
customers-service → repository
notifications → repository
common-library → repository
frontend → repository
infrastructure → repository
But Git repository boundaries don't necessarily equal architectural boundaries.
A system can have:
- 50 repositories
- 20 deployable services
- 5 teams
- shared libraries
- tightly coupled release processes
and still effectively behave like one system.
Likewise, a monorepo can contain completely independent applications owned by different teams.
So the real decision is not:
"How many Git repositories should we have?"
It is:
"Where should we place boundaries between teams, code, dependencies and delivery pipelines?"
That's a much more useful question.
2. Monorepo: What It Gives You
A monorepo provides one major capability that becomes extremely valuable as systems evolve:
You can see and change the system as one codebase.
Imagine we have:
orders-service
payments-service
customer-service
and we need to change an API contract.
In a monorepo:
Change API contract
↓
Update producer
↓
Update consumers
↓
Compile/test everything affected
↓
Review one change
↓
Merge
The compiler and CI system can help us identify the blast radius.
This becomes particularly valuable for:
- API contract changes
- shared libraries
- security changes
- large refactoring
- framework upgrades
- dependency upgrades
- consistent engineering standards
For example:
Java 17 → Java 21
↓
orders
payments
customers
notifications
reporting
A monorepo makes it much easier to discover what needs to change.
3. The Hidden Superpower: Refactoring
This is one of the biggest practical advantages of a monorepo.
Suppose we have:
CustomerService.getCustomer()
used by:
Orders
Payments
Invoices
Reporting
Notifications
Now imagine changing:
getCustomer()
to:
getCustomer(CustomerId id)
In a monorepo, the IDE/compiler/test system can often identify the impact immediately.
With multiple repositories, you may need to:
- Change the shared library
- Publish a new version
- Update repository A
- Update repository B
- Update repository C
- Wait for their CI pipelines
- Deal with version compatibility
- Eventually remove the old API
The technical change may be tiny.
The organisational change can be much larger.
4. But Monorepos Are Not Free
A monorepo can become painful if we don't invest in engineering tooling.
Imagine this:
monorepo
│
├── 100 services
├── 20 libraries
├── 10 frontend applications
└── millions of lines of code
A developer changes one line in:
payments-service
and CI responds with:
Build everything
Test everything
Deploy everything
Now the repository becomes the bottleneck.
This is where people sometimes incorrectly conclude:
"Monorepos don't scale."
The more accurate conclusion is:
Naive monorepo tooling doesn't scale.
A serious monorepo needs:
- dependency graphs
- incremental builds
- affected-test detection
- remote build caching
- parallel CI
- clear ownership
- CODEOWNERS
- good developer tooling
The goal should be:
Developer changes payments
↓
Dependency graph
↓
Identify affected components
↓
Build/test only what matters
Not:
Developer changes payments
↓
Build the entire company
5. Polyrepo: Why Teams Choose It
Polyrepo usually starts with a very reasonable requirement:
"This team should be able to own and deploy its service independently."
For example:
Team A
└── orders-service
Team B
└── payments-service
Team C
└── notifications-service
Each team gets:
- its own repository
- its own CI pipeline
- its own deployment lifecycle
- its own permissions
- its own release cadence
This can work extremely well.
Especially when services have genuinely independent ownership.
6. The Hidden Cost of Polyrepo
The problem appears when repositories are technically separate but operationally coupled.
Imagine:
orders-service
↓
payments-service
↓
customer-service
↓
shared-library
Now a breaking change happens in the shared library.
The change becomes:
shared-library v2
↓
orders → update
payments → update
customer → update
reporting → update
notifications → update
Suddenly we have a distributed coordination problem.
The repositories are separate.
The architecture isn't.
This can lead to:
- dependency version drift
- duplicated code
- inconsistent frameworks
- inconsistent security fixes
- incompatible API versions
- difficult cross-repository refactoring
- pull-request coordination across teams
7. One of the Biggest Lessons: Microservices ≠ Polyrepo
This is worth calling out explicitly.
A common architecture evolution looks like this:
Monolith
↓
Microservices
↓
One repository per microservice
The last step is not mandatory.
You can absolutely have:
Monorepo
│
┌────────────┼────────────┐
↓ ↓ ↓
Orders Payments Customers
│ │ │
└────── independently ────┘
deployable
The deployment boundary and repository boundary can be different.
That's an important architectural distinction.
8. Real Engineering Scenario: Shared Libraries
Let's look at a common example.
Suppose we have:
common-auth
common-logging
common-observability
common-http
In a polyrepo architecture, these often become separate repositories.
Then teams consume versions:
common-auth: 1.4.2
common-logging: 3.1.0
common-http: 2.7.1
Over time:
Service A → auth 1.4
Service B → auth 1.5
Service C → auth 1.3
Service D → auth 1.2
Now the organisation has effectively created multiple versions of the same engineering standards.
This isn't automatically wrong.
Sometimes independent versioning is exactly what we want.
But we should recognise the trade-off.
9. A Useful Rule for Shared Libraries
Ask:
Does this library represent a reusable capability or an organisational coupling mechanism?
For example:
Good candidate
Observability SDK
It may evolve independently.
Potentially problematic
CompanyBusinessLogicUtils
This can become a dumping ground for code that should actually belong to a domain.
A common anti-pattern is:
common/
├── StringUtils
├── DateUtils
├── CustomerUtils
├── PaymentUtils
├── OrderUtils
├── ValidationUtils
└── EverythingElseUtils
Eventually every service depends on common.
Congratulations.
You've created a distributed monolith with a shared library.
10. Repository Boundaries Should Follow Ownership
One of the strongest signals for repository boundaries is team ownership.
Imagine:
Team: Payments
Owns:
├── payments-api
├── payment-processing
├── reconciliation
└── payment-risk
These components may benefit from being in the same repository.
Because the same team:
- changes them together
- reviews them
- deploys them
- understands their dependencies
Now imagine:
Payments team
↓
Customer team
↓
Identity team
If every change requires three teams to coordinate, the problem may not be the repository.
It may be the architecture.
11. Repository Boundaries Should Follow Change Patterns
This is one of the questions I now ask when evaluating repository structure:
What code tends to change together?
If these always change together:
orders
orders-api
orders-domain
orders-tests
keeping them together makes sense.
If these change independently:
marketing-website
payment-processing
internal-data-platform
there is little value in forcing them into one repository purely for consistency.
A useful mental model is:
High change coupling
↓
Keep closer together
Low change coupling
↓
Separate when useful
12. CI/CD Changes the Equation
Repository strategy and CI/CD strategy are tightly connected.
A monorepo without good CI/CD becomes painful.
A polyrepo without standardised CI/CD also becomes painful.
Imagine 80 repositories.
If every repository has:
Jenkinsfile
Dockerfile
Terraform
security-scan.yml
deployment.yml
and every team maintains its own version, you eventually get:
Repo A → Java 17
Repo B → Java 21
Repo C → old security scanner
Repo D → custom deployment
Repo E → completely different pipeline
The repository structure isn't necessarily the problem.
Lack of platform engineering is.
A mature organisation should provide reusable delivery capabilities.
For example:
Platform Engineering
│
┌───────────┼───────────┐
↓ ↓ ↓
Build Security Deploy
│ │ │
└───────────┼───────────┘
↓
Product Teams
Teams should be able to own their applications without having to reinvent the delivery platform.
13. Developer Experience Matters More Than We Think
Ask a developer:
"How long does it take you to make a change across three services?"
That's often more useful than asking:
"Do we use a monorepo?"
Consider the workflow.
Monorepo
Clone
↓
Find services
↓
Make change
↓
Run affected tests
↓
Single PR
Polyrepo
Clone repo A
↓
Clone repo B
↓
Clone repo C
↓
Change shared contract
↓
PR A
↓
PR B
↓
PR C
↓
Version library
↓
Update dependencies
↓
Wait for pipelines
Neither is inherently better.
The important question is:
Which workflow reflects how your engineers actually work?
14. Security and Compliance Can Change the Decision
There are cases where separate repositories make practical sense.
For example:
Public product
│
├── repository A
Highly restricted system
│
└── repository B
Different access requirements can be a legitimate reason for separation.
Other examples include:
- regulated workloads
- sensitive infrastructure
- different organisational ownership
- acquisition boundaries
- external/open-source projects
- different security classifications
This is where repository boundaries can become more than a developer-experience decision.
15. The Hybrid Model
In larger organisations, the answer doesn't have to be:
Everything → monorepo
or:
Everything → polyrepo
A hybrid approach can be very practical.
For example:
Engineering Organisation
│
┌───────────────────┼───────────────────┐
↓ ↓ ↓
Product Monorepo Platform Repo Data Platform
│ │ │
┌────┼────┐ │ ┌────┼────┐
↓ ↓ ↓ ↓ ↓ ↓ ↓
API Worker UI Infrastructure ETL ML Analytics
The key is that each boundary has a reason.
Not:
"We have always done it this way."
16. A Practical Decision Framework
Instead of asking:
"Which is better?"
I prefer asking these questions.
| Question | Signal |
|---|---|
| Do multiple teams frequently modify the same code? | Monorepo |
| Do services have genuinely independent ownership? | Polyrepo |
| Are cross-service refactors common? | Monorepo |
| Do systems have different security/access boundaries? | Polyrepo |
| Do components share many libraries? | Monorepo can simplify this |
| Do teams have very different release lifecycles? | Polyrepo |
| Can CI efficiently build only affected components? | Makes monorepo more viable |
| Do teams require strong repository-level isolation? | Polyrepo |
| Are services actually independent despite being in one repo? | Monorepo can still work |
| Are many repos creating duplicated tooling? | Consider consolidation/platform tooling |
The important thing is that these are signals, not absolute rules.
17. What I Would Avoid
Anti-pattern 1: "Microservices means one repo per service"
Not necessarily.
Repository boundaries and service boundaries solve different problems.
Anti-pattern 2: "Monorepo means one giant application"
Also not necessarily.
A monorepo can contain independently deployable services.
Monorepo
│
┌────────────┼────────────┐
↓ ↓ ↓
Service A Service B Service C
│ │ │
↓ ↓ ↓
Deploy A Deploy B Deploy C
Anti-pattern 3: "Every shared class belongs in common"
This usually creates coupling.
Before creating a shared library, ask:
Is this genuinely reusable infrastructure, or are we hiding domain ownership problems?
Anti-pattern 4: "Every repository needs its own pipeline"
Standardise the platform.
Teams should own their delivery outcomes without necessarily owning every line of CI/CD implementation.
Anti-pattern 5: "Repository count is an architecture metric"
It isn't.
I'd rather know:
- deployment frequency
- lead time for change
- change failure rate
- dependency coupling
- build time
- test time
- ownership clarity
- developer productivity
than simply:
"We have 47 repositories."
18. What I'd Do on a New Project
If I were starting a new product today, I wouldn't begin with:
"Let's choose monorepo."
or:
"Let's choose polyrepo."
I'd start with:
Step 1 — Identify teams
Who owns what?
Step 2 — Identify deployment boundaries
What needs independent deployment?
Step 3 — Identify change coupling
What changes together?
Step 4 — Identify security boundaries
What requires separate access?
Step 5 — Understand developer workflows
How will engineers build and test changes locally?
Step 6 — Design CI/CD accordingly
Can we build/test only what changed?
Step 7 — Choose repository boundaries
Only now would I decide:
monorepo
OR
polyrepo
OR
hybrid
The repository strategy becomes an outcome of the architecture rather than the starting point.
19. My Rule of Thumb
If I had to reduce the whole discussion to one principle:
Put things together when they need to change together. Separate things when they need to be owned, secured, or released independently.
And there is an important second principle:
Don't use repository boundaries to solve problems that actually belong to architecture, ownership, or platform engineering.
A monorepo won't fix poor service boundaries.
A polyrepo won't create team autonomy by itself.
Microservices won't automatically give independent deployment.
And multiple Git repositories won't automatically create a scalable engineering organisation.
20. The Takeaways
If you're deciding between monorepo and polyrepo, I'd keep these points in mind:
1. Repository boundaries are architectural boundaries
Treat them deliberately.
2. Microservices do not require polyrepos
Services can be independently deployable from a monorepo.
3. Monorepos need good tooling
Incremental builds, dependency graphs, caching and affected-test execution become important at scale.
4. Polyrepos need platform engineering
Otherwise every team ends up maintaining its own CI/CD, security and developer tooling.
5. Shared libraries can create hidden coupling
Especially when they contain business logic.
6. Team ownership matters
Repository boundaries should make ownership clearer, not more complicated.
7. Look at change patterns
Ask what actually changes together.
8. Optimise for developer flow
The best architecture is often the one that reduces unnecessary coordination while preserving the boundaries the organisation genuinely needs.
Final Thought
The monorepo vs polyrepo debate is often presented as if there is a universally correct answer.
There isn't.
The better question is:
"What repository structure allows our teams to change the system safely, independently and efficiently?"
Once you start looking at the problem through:
ownership → coupling → change patterns → security → deployment → developer experience
the decision becomes much less ideological and much more engineering-driven.
And that's probably the most important lesson:
Choose repository boundaries based on how your organisation and software actually change — not based on what architecture is fashionable today.
Top comments (0)