Why Monorepos Break Down at Scale (And What to Do Instead)

Why Monorepos Break Down at Scale (And What to Do Instead)

UnknownBy Unknown
Architecture & Patternsmonoreporepository architecturecode organizationdeveloper productivityscaling

This guide explains why monorepo architectures that work beautifully for small teams often collapse under their own weight as codebases grow—and presents practical strategies for restructuring your repositories without sacrificing developer velocity. You'll learn the warning signs of monorepo failure, alternative approaches that preserve collaboration benefits, and how to migrate incrementally without halting feature development.

Monorepos gained massive popularity over the past decade. Google popularized the concept—managing billions of lines of code in a single repository—and tools like Nx, Turborepo, and Rush made the approach accessible to smaller teams. The pitch is compelling: shared code visibility, atomic commits across projects, simplified dependency management, and unified tooling. For startups and mid-sized companies, this feels like a no-brainer.

But here's what nobody tells you: the complexity curve isn't linear. A monorepo that feels snappy with fifty developers becomes molasses with five hundred. Build times balloon. CI pipelines stretch from minutes to hours. Merge conflicts multiply. Tooling that once "just worked" starts requiring dedicated platform teams to maintain. The same architecture that accelerated your early growth becomes the thing slowing you down—and breaking it apart isn't as simple as running git filter-repo and calling it a day.

Why Does My Build Take 45 Minutes Now?

Incremental builds were supposed to solve this. Tools like Bazel and Nx promise to rebuild only what changed—yet somehow, your developers are still waiting half an hour for CI to pass on a one-line documentation fix. What happened?

The culprit is usually invisible dependency chains. In a healthy codebase, modules have clear boundaries. In a stressed monorepo, everything depends on everything else—not by design, but by convenience. Someone needed a utility function, so they imported it from a seemingly unrelated package. Six months later, that "seemingly unrelated" package now imports half the codebase. Your build graph, once a tidy tree, resembles a hairball.

Detecting these chains requires more than gut feeling. Tools like Nx's dependency graph or Dependency Cruiser can visualize the mess, but visualization alone doesn't fix it. You'll need to institute module boundaries—architectural rules that prevent circular dependencies and enforce layering. This isn't about bureaucracy; it's about giving your build system room to do its job.

Another hidden cost: test execution. In theory, you run only tests for changed packages and their dependents. In practice, flaky tests in distant parts of the codebase force teams to run everything "just to be safe." Address flakiness aggressively—it's not just an annoyance, it's a tax on every developer's time. Consider test sharding and parallel execution, but recognize these are bandages on a deeper wound.

Is There a Middle Ground Between One Repo and Many?

The binary choice—monorepo versus polyrepo—misses the nuance of how modern engineering organizations actually work. You don't have to pick a side. Several hybrid approaches preserve the benefits of code sharing while avoiding the pitfalls of giant repositories.

The "monorepo of monorepos" pattern (sometimes called a "meta-repo") groups related services into separate repositories, then uses a parent repository with git submodules or tools like repo to coordinate them. Each team owns their repository's structure and tooling, but cross-cutting changes remain possible. Amazon famously uses this approach—hundreds of repositories, organized logically, with shared standards but independent release cycles.

Another emerging pattern: the "package-oriented monorepo." Instead of co-locating every service and application, you maintain a monorepo strictly for shared libraries and design systems. Applications live in their own repositories, consuming published packages from the shared repo. This keeps your build times manageable—libraries change less frequently than applications—and creates natural versioning boundaries that force teams to think about backward compatibility.

The "skeletal monorepo" takes this further. Keep only build configuration, shared types, and API contracts in the monorepo. Implementation code lives elsewhere, referenced at build time. This isn't for everyone—it requires sophisticated tooling—but for organizations at massive scale, it solves the fundamental problem: you want shared understanding without shared everything.

How Do You Migrate Without Breaking Everything?

Extracting code from a monorepo is like performing surgery on a beating heart. You can't shut down development for three months while you reorganize. The migration must happen incrementally, with clear ownership and rollback plans at every step.

Start by identifying natural boundaries. Which packages have the fewest dependents? Which teams already operate with relative independence? These are your candidates for extraction. Don't begin with your most interconnected module—that's a recipe for months of pain. Build confidence (and tooling) on simpler extractions first.

The technical mechanics aren't difficult. Tools like git-filter-repo preserve history when splitting repositories. Package managers handle version resolution. The hard part is the human coordination: updating documentation, retraining developers, establishing new code review practices, and—crucially—deciding who owns what. Every extracted repository needs an owner. Without clear ownership, you haven't solved your problem—you've just moved it somewhere less visible.

Maintain cross-repository dependencies carefully. When Package A in Repo X depends on Package B in Repo Y, you need versioning discipline. Semantic versioning helps, but only if you actually follow it. Consider automated dependency update tools like Dependabot or Renovate—they'll surface breaking changes early rather than surprising you during a release crunch.

When Should You Actually Split?

Not every slow build warrants repository surgery. Sometimes the answer is better caching, more powerful CI runners, or—let's be honest—cleaning up the mess your team made. Before committing to a split, ask:

  • Are build times the real constraint, or are developers just frustrated with flaky tooling?
  • Does your organization have the maturity to manage multiple repositories without chaos?
  • Have you exhausted optimization opportunities within your current structure?
  • Can you articulate specific boundaries—what goes where and why?

If you can't answer that last question with specificity, you're not ready. Repository splits are expensive to reverse. Better to live with a slightly-too-large monorepo than to scatter your codebase across dozens of repos with no coherent strategy.

What About Code Sharing Across Repositories?

This is where many polyrepo migrations stumble. Teams extract their services, then realize they need shared utilities, types, or configuration. They copy-paste code. Versions drift. Bugs get fixed in one place and persist in others. The cure—decoupled repositories—starts to feel worse than the disease.

Private package registries solve this. Whether you're using npm, PyPI, or an internal artifact store, publish shared code as versioned packages consumed by your other repositories. This introduces friction—now there's a publish step, a version bump, a potential lag between changes and availability—but that friction is intentional. It forces you to treat shared code as a product with consumers, not just an implementation detail.

For truly shared infrastructure—deployment configurations, security policies, CI templates—consider a "platform repository" pattern. A dedicated team maintains standards and tooling, published as packages or referenced via Git submodules. Other repositories opt into these standards voluntarily but can override when necessary. This balances consistency with autonomy better than either extreme.

Where Do We Go From Here?

There's no universal answer. Google's monorepo works for Google because they've spent twenty years building custom tooling and culture around it. Your fifty-person startup doesn't have those resources—and probably doesn't need them. Start simple. Add complexity only when the pain justifies it. And never forget that repositories are organizational tools, not technical mandates. Structure them to match how your teams actually work, not how you think they should work.

The best repository architecture is the one your team can maintain without heroic effort. Sometimes that's a monorepo. Sometimes it's fifty separate repos. Most often, it's something in between—evolving as your organization grows, contracts, and changes direction. Stay pragmatic, measure what matters, and don't let architectural purity become the enemy of shipping code.