Monorepo vs Polyrepo

Deciding whether to keep multiple projects or packages in one shared repository, or spread across separate repositories, and weighing the tradeoffs.

What is it?

Once a system grows past a single application — say, a web frontend, a backend API, and a couple of shared libraries — you face a structural question that has nothing to do with classes or layers: how many git repositories should all of this live in?

Put everything — every app and every shared package — into one single repository, and you have a monorepo. A change that touches both the frontend and a shared library can be made and reviewed in one commit, one pull request. Every project always sees the very latest version of every shared package, because there's only one copy of it. The tradeoff is that the repository grows large, tooling (build systems, CI) has to be taught to handle multiple projects efficiently, and it's easier for teams to accidentally step on each other's code.

Split each project into its own separate repository, and you have a polyrepo. Each team or project gets a clean, focused, independently versioned space with clear ownership boundaries — nobody's unrelated change shows up in your pull request history. The tradeoff is that sharing code between repos requires publishing and versioning packages (instead of just importing a local file), and a change that spans multiple repos means coordinating multiple pull requests, multiple reviews, and multiple release timings, which is significantly more overhead.

Neither is universally "correct" — it's a genuine tradeoff, and large companies run successfully with both approaches (and some run a mix).

Explain like I'm 10

A monorepo is like one large shared office where every team sits in the same building — easy to walk over and coordinate, but you need clear floor plans (tooling) so teams don't clutter each other's space. A polyrepo is like separate office buildings for each team — cleaner boundaries, but scheduling a meeting between two buildings takes more coordination.

Examples

Polyrepo: sharing code requires publishing a package

// In repo "shared-utils" (its own repository, its own version)
// package.json: { "name": "@acme/shared-utils", "version": "1.4.0" }
export function formatCurrency(cents) { /* ... */ }

// In repo "web-app" (a separate repository)
// package.json: "dependencies": { "@acme/shared-utils": "^1.4.0" }
import { formatCurrency } from "@acme/shared-utils";
// To pick up a bugfix in formatCurrency, web-app must bump its
// dependency version and go through its own release process.

A fix to formatCurrency has to be released as a new package version before web-app can use it — two separate steps, two separate repos, two separate review processes.

Monorepo: sharing code is a direct import

// One repository, multiple folders
// packages/shared-utils/formatCurrency.js
export function formatCurrency(cents) { /* ... */ }

// apps/web-app/checkout.js
import { formatCurrency } from "../../packages/shared-utils/formatCurrency";
// A fix to formatCurrency is visible to web-app immediately,
// in the very same pull request if needed.

The fix and its usage can be reviewed and merged together, atomically, with no separate release step — at the cost of both projects now living in, and being built by tooling for, one shared repository.

How it works

The practical difference comes down to how code crosses a project boundary. In a polyrepo, crossing that boundary always means going through a published, versioned package — which adds process but also adds a deliberate checkpoint (you choose when to upgrade). In a monorepo, crossing that boundary is just an import statement — which removes that checkpoint (everyone is always on the latest version) but requires tooling that can figure out which parts of a huge repository actually need to be rebuilt or retested for a given change, since naively rebuilding everything on every commit doesn't scale.

Why does it exist?

This is a genuinely open tradeoff, not a solved problem, because the two costs it balances — coordination overhead across repos vs. tooling and organizational overhead within one giant repo — both scale with the size of an organization, but in opposite directions. Small, tightly-coordinated teams tend to feel the coordination overhead more; large organizations with many independent teams tend to feel the "everyone touches one giant repo" overhead more, which is why both models remain in active, successful use at scale.

When to use it

A monorepo tends to fit well when multiple projects share a lot of code and evolve together, when you want atomic changes across project boundaries, and when you're willing to invest in tooling to keep builds and tests fast as the repository grows. It also removes the "which version of the shared library is everyone on" drift that polyrepos can suffer from.

When not to use it

A polyrepo tends to fit better when projects are genuinely independent (different release cycles, different teams with little overlap, or even different companies/open-source consumers), where a monorepo's shared tooling and shared access would add coordination cost without a matching benefit, or where you specifically want strict, deliberate version boundaries between components.

Common mistakes

  • Adopting a monorepo without investing in the tooling (like build caching or affected-project detection) needed to keep it fast, leading to painfully slow CI as it grows.

  • Adopting a polyrepo for tightly-coupled projects that change together constantly, resulting in a stream of synchronized, multi-repo pull requests for nearly every feature.

  • Treating the choice as permanent and unquestionable, rather than revisiting it as the number of teams, projects, and their degree of shared code changes over time.

Practice exercises

  1. Easy:

    List two concrete costs and two concrete benefits of a monorepo, and two of each for a polyrepo, from the explanation above, in your own words.

  2. Medium:

    A company has one web frontend, one mobile app, and one shared design-system package that both consume, all built by the same small team. Argue for whether a monorepo or polyrepo fits better, and why.

  3. Hard:

    Describe what tooling problem a very large monorepo (thousands of projects) needs to solve that a small one doesn't, and outline (at a high level) one strategy for solving it.

Interview questions

What is the core tradeoff between a monorepo and a polyrepo?

A monorepo makes cross-project changes atomic and keeps everyone on the same version of shared code, at the cost of needing tooling to scale and clearer internal boundaries; a polyrepo gives each project a clean, independently-versioned, independently-owned space, at the cost of needing package publishing and multi-repo coordination for shared changes.

Why does a monorepo need special build tooling as it grows?

Because rebuilding and re-testing every project on every commit doesn't scale — tooling needs to determine which projects were actually affected by a given change and only rebuild/retest those.

In a polyrepo setup, how does one project pick up a bugfix made in a shared library?

The shared library publishes a new version of its package, and the consuming project updates its dependency version and goes through its own release process — it's not automatic the way a local import would be.