Why I Hate Monorepos

I need to get something off my chest: I hate monorepos.

Not "I mildly prefer multiple repos." Not "monorepos have tradeoffs." I think they're actively harmful for most teams and I talk clients out of them regularly.

Before you grab your pitchforks, let me explain. More importantly, let me tell you what I do instead that actually works.

The Monorepo Trap

Here's how it usually goes:

Month 1 -"We should use a monorepo! Google's doing it, it'll simplify dependencies!"

Month 6 - "Okay, the build times are a bit slow, but we're saving so much time on integration!"

Month 12 - "Why does changing a comment in service A trigger a 45-minute build for services B, C, and D?"

Month 18 - "Who broke production? Oh, someone merged a PR that touched 47 services and we only tested 3 of them."

Month 24 - "We're splitting into separate repos. Again."

I've seen this cycle at least a dozen times. The monorepo promise is seductive: one repo to rule them all, atomic commits across services, shared libraries just a directory away. But the reality rarely matches the pitch.

You Aren't Google (And That's Okay)

In 2016, Google published a paper with the catchy headline "Why Google stores billions of lines of code in a single repository." Suddenly everyone wanted to be Google. Teams I worked with started chanting "If it's good enough for Google..." like it was scripture.

Here's the problem: you're not Google. Let me show you what Google was dealing with when they made that choice:

  • 1 billion files
  • 35 million commits spanning 18 years
  • 86 terabytes of data
  • 250,000 files changed weekly
  • 25,000 developers worldwide

Most importantly: Git didn't even exist when Google started down this path. They built their own custom system called Piper on top of Bigtable and Spanner, distributed across 10 data centers. Nobody clones Google's repo. They use a FUSE filesystem that streams files from the cloud.

Oh, and Google doesn't use monorepos for everything. Chrome and Android are split across multiple Git repos. Even Google knows when to say no.

When you copy Google's monorepo strategy with Git and GitHub Actions, you're not copying Google. You're cargo-culting the aesthetics without the infrastructure.

What Monorepos Actually Cost You

Let's be honest about the costs:

1. Build Time Explosion

In a monorepo, everything is connected whether you like it or not. Change a shared utility? Time to rebuild everything that might possibly use it. The dependency graph becomes your worst enemy.

I've seen teams with "simple" monorepos where a one-line change triggers 2-hour build pipelines. When your feedback loop is measured in hours, people start skipping steps. Quality goes out the window.

2. The Blast Radius Problem

When everything's in one repo, everything's connected. A broken commit in service A can block deployments for services B, C, and D, even if they're completely unrelated.

The "we'll just use good testing" argument falls apart when you realize that comprehensive testing across 50 services takes hours and costs a fortune in CI compute. So teams skip it. And then things break.

3. Access Control Nightmares

Not everyone should see everything. Your contractor working on the frontend probably shouldn't have access to the payment processing code. Your intern definitely shouldn't be able to accidentally push to production services.

Monorepos make this hard. You end up with complex path-based access controls that are brittle and error-prone. Or you just give everyone access to everything and hope for the best. (Spoiler: hope is not a strategy.)

4. Tooling Complexity

Need to tag a release? Good luck when your repo contains 47 different services all at different versions. Want to use GitHub's security features? Enjoy trying to configure them for a codebase that's actually 30 different applications.

You end up needing specialized monorepo tooling: Bazel, Nx, Turborepo, custom scripts. Now instead of solving business problems, you're managing build tooling. Congratulations, you've accidentally created a platform team whose entire job is making Git work.

5. The Cognitive Load

When I open a monorepo, I'm faced with thousands of files across dozens of services. Finding anything requires deep knowledge of the directory structure. Onboarding new developers takes weeks because they need to understand the entire codebase just to work on one service.

Compare that to a focused repo: here's the service, here are its dependencies, here's how to run it. Simple.

What I Do Instead

I use multiple repos with strong contracts. Here's the setup:

One Deployable Artifact Per Repo

Each service gets its own repository. Period. If it deploys independently, it lives independently.

This gives you fast builds (only build what changed), clear ownership (this team owns this repo), simple access control (GitHub permissions work normally), and focused code reviews where you're only looking at one service.

Shared Libraries Are Real Libraries

When services share code, that code becomes a proper library with its own repo and versioning.

mycompany-auth-library/
  v1.2.3 (tagged release)
  
service-a/
  uses mycompany-auth-library@v1.2.3
  
service-b/
  uses mycompany-auth-library@v1.2.1 (upgrading soon)

No magic directory imports. No "just reference the shared folder." Real dependency management with real versions.

This forces you to think about backward compatibility and API stability. It also means you can upgrade services individually instead of the "big bang" monorepo update.

API Contracts Over Code Sharing

The monorepo pitch often includes "easy code sharing" as a benefit. But services shouldn't share implementation details; they should communicate through APIs.

Instead of importing a function from service B into service A, service A calls service B's API. The contract is explicit, documented, and stable.

Yes, there's overhead in defining APIs. But that overhead exists whether you're in a monorepo or not. At least with separate repos, you're forced to acknowledge it. Tools like Pact make this even more practical by letting you verify contracts between services automatically, so you know immediately when a provider breaks a consumer's expectations.

When you need to roll out a breaking change across multiple services, you do it incrementally. Update the library, roll it out service by service, verify each one. It's more deliberate than the monorepo "change everything at once and pray" approach, and significantly safer.

Tooling That Actually Works

With multiple repos, standard tools work normally. Git tags make sense (one repo equals one version). GitHub Actions are simple (just build this service). Code reviews focus only on relevant changes. Security scanning is scoped to just this service's code.

If you're doing GitOps, separate repos map cleanly to the model. One repo per service means one Flux Kustomization or ArgoCD Application per repo, clean image automation, clean environment promotion. Monorepos turn GitOps into a mess of path filters and custom ignore rules. I've seen teams spend weeks trying to get Flux to only reconcile the parts of a monorepo that actually changed. With separate repos, it just works.

You spend less time fighting your tools and more time building features.

When Monorepos Actually Make Sense

I'm not a zealot. There are cases where monorepos are the right choice.

  1. Massive scale - If you're Google with thousands of engineers and dedicated tooling teams, the coordination benefits might outweigh the costs.
  2. Tight coupling - If your "services" are actually just one application split into modules that always deploy together, a monorepo might be simpler.
  3. Atomic changes - If you genuinely need to update 20 services simultaneously and can't do it incrementally, a monorepo makes that easier. Though I'd argue you should fix your architecture instead.

But here's the thing: you're probably not Google. And your services probably shouldn't be that tightly coupled.

But We're Already in a Monorepo!

I've helped teams migrate out of monorepos. It's not as scary as it sounds.

Start by identifying your domain bounded contexts. Look for natural boundaries in your codebase where services have clear responsibilities and minimal entanglement with other parts of the system. From there, pick the least coupled bounded context, the one with the fewest dependencies and clear boundaries. This is your first extraction target.

Why the least coupled? It requires the least amount of work to extract. No untangling complex shared state, no refactoring half a dozen other services. And once it's out, it's out of your way. You've reduced the monorepo's size and complexity, and you've proven the extraction process works.

Extract it to a new repo (keep the Git history if you care). Publish it as a library if other services import its code. Update the monorepo to use the library version instead of the local copy. Then repeat for the next bounded context.

It's gradual. Each extraction makes the remaining monorepo smaller and more manageable. Eventually you're left with either a small monorepo of genuinely coupled services, or no monorepo at all. Either outcome is better than where you started.

Real Talk

Monorepos are a tool. They're not inherently evil. But they're a tool for a specific set of problems that most teams don't have.

The industry latched onto monorepos because Google uses them, and everyone wants to be Google. But Google has thousands of engineers, custom-built tooling like Bazel and Piper, dedicated infrastructure teams, and different scaling challenges than your startup.

You're not Google. Your problems are different. Your solutions should be too.

Don't let industry trends dictate your architecture. Start with the simplest thing that works: separate repos, clear boundaries, explicit contracts. Only add complexity when you have a real problem that simple solutions can't solve.

Most teams I work with are happier after ditching their monorepo. Their builds are faster. Their deploys are safer. Their developers are less confused. The ones who kept their monorepos either actually needed them (rare), or weren't willing to do the work to extract services (understandable, but still paying the cost).

You get to choose which group you're in.


Agree? Disagree? Think I'm an idiot? Drop a comment and tell me about your monorepo experience, good, bad, or ugly. I'm genuinely curious if your experience differs from mine.