skip to content

How does working in a monorepo change the mechanics of making a breaking change to a widely-used shared library, compared to making that same change in a polyrepo where the library is a separately versioned package?

level: middleimportance: should knowfreq 55%

answer

  1. atomic commit updates library + all call sites at once
  2. semver + published package = deferred, opt-in upgrade
  3. large-scale-change (LSC) tooling at Google
  4. diamond dependency / left-pad as the polyrepo failure mode

basics

~20 s

In a monorepo, you can update the shared code AND every place that uses it in one single commit, so nothing breaks. In a polyrepo, you publish a new version of the library, and every other team has to separately update to it later, so there can be a gap where things are out of sync.

solid answer

~50 s

In a monorepo, a breaking API change and every call-site update land in one atomic commit, so the repository is never in an inconsistent state -- the build system can even enforce this by refusing to merge if any dependent target fails to build against the new signature. This is 'tight coupling' in the literal sense: the shared library and its consumers are pinned to the exact same commit, always. In a polyrepo, the library is published as a versioned package (semver); consumers pin a version and upgrade on their own schedule, so a breaking change ships as a new major version while old consumers keep working against the old one until they choose to upgrade. This is looser coupling -- consumers get temporal independence -- but it means the library maintainer can't simply delete the old behavior; they must support both versions until every consumer has migrated, and 'who's still on the old version' has to be tracked instead of being self-evident.

go deeper

for a junior

Should articulate the basic shape: monorepo updates everything together in one change, polyrepo updates the library first and consumers catch up later on their own schedule.

for a middle

Should connect this to semantic versioning as the polyrepo's coordination mechanism, and recognize that monorepo atomic updates mean the library owner is responsible for fixing call sites.

for a senior

Should discuss the failure modes concretely -- diamond dependencies and stale/unpatched versions on the polyrepo side, unreviewable giant commits or blocked merge queues on the monorepo side -- and know that large-scale-change tooling exists to make the monorepo side tractable.

for a principal

Should be able to describe organizational trade-offs: monorepo coupling concentrates review/coordination cost at change time but keeps the system always consistent; polyrepo coupling defers cost to long-tail version support and requires investing in automated upgrade propagation to avoid drift becoming a security liability.

## What actually differs The mechanical difference comes down to when consumers of shared code are forced to observe a change. ## In a monorepo: one atomic commit In a monorepo, there is exactly one canonical copy of the shared library's source, referenced by other targets directly via the build graph (e.g., a dependency entry pointing at a source target, not a resolved package version). When someone changes that library's public API, the build system can, and typically does, refuse to merge unless every dependent target in the repository still compiles and its tests still pass against the new signature. In practice this means the person making the breaking change is also responsible for updating every call site in the same commit (or a tightly sequenced series of commits) -- the classic **'large-scale change'** or **LSC** workflow used at Google, where tooling helps auto-generate the mechanical call-site fixes and a single commit lands atomically across potentially thousands of files. The repository is never in a state where the library and its consumers disagree about the API, because there is only one version of 'now.' ## In a polyrepo: a published, versioned artifact In a polyrepo, the shared library lives in its own repository and is consumed elsewhere as a versioned, published artifact: - an npm package - a Maven jar - a PyPI wheel Each consuming repository declares a dependency on a specific version (or version range) in its own manifest, and upgrading is a separate, deliberate act performed by that consumer's own team on their own schedule. This is what **'loose coupling'** buys you: a consumer's build doesn't break the moment the library author pushes a change, because the consumer simply isn't looking at that change yet. Semantic versioning exists precisely to communicate the shape of that change without forcing anyone to look at code -- a major version bump signals 'breaking, migrate deliberately,' a minor/patch bump signals 'safe to pull automatically.' ## Where the pain gets displaced The trade-off is where the pain gets displaced to, not eliminated. - **Monorepo-style atomic coupling makes the library maintainer's job harder up front.** They cannot ship a breaking change without either fixing every consumer themselves or coordinating a synchronized update, which for a library used by hundreds of teams can be a genuinely large undertaking (this is exactly why large-scale-change tooling exists -- to make that undertaking mechanically tractable rather than a multi-week manual slog). It also means a single bad or slow-to-review breaking change can become a bottleneck blocking many unrelated teams' work if the merge queue serializes on it. - **Polyrepo-style versioned coupling makes the consumer's job the deferred cost.** Nothing forces them to upgrade, so the library maintainer typically has to support N-1 (or more) old major versions in parallel for a long tail of stragglers, maintaining multiple code paths, backporting security fixes to old branches, and eventually forcibly deprecating versions that never got migrated. ## The mirror-image failure modes The failure modes on each side are the mirror image of each other. - **On the monorepo side**, without large-scale-change tooling and enforced 'you break it, you fix all call sites' discipline, teams either avoid making necessary breaking changes at all (accumulating cruft) or push through changes that are technically atomic in the commit but practically reviewed by nobody who understood every affected system, because one PR touched a thousand files across forty teams. - **On the polyrepo side**, the failure mode is the **'diamond dependency'** and long-tail-version problem: package A depends on library X version 1, package B depends on library X version 2, and something that transitively needs both A and B either can't resolve a single compatible version or silently ends up with two copies of X in memory; separately, security patches to X often don't reach every consumer promptly because upgrading is opt-in and unenforced, which is exactly the scenario dependency-bump bots like Renovate and Dependabot exist to shrink by opening automatic upgrade PRs. ## Anchoring both sides in the real world A concrete real-world anchor for the tight-coupling side: Google's internal Large-Scale Change process routinely lands single changes touching tens of thousands of files across a codebase with hundreds of millions of lines, specifically because the monorepo plus Piper/Blaze tooling makes atomic, all-consumers-updated-together changes tractable. A concrete anchor for the loose-coupling side: the JavaScript/npm ecosystem, where a single popular package (the 'left-pad' incident being a canonical cautionary tale) can be depended on, at different pinned versions, by thousands of downstream packages simultaneously, and where version-resolution conflicts and stale, unpatched transitive dependencies are a well-known, ongoing operational and security concern that tooling (lockfiles, audit commands, automated bump bots) exists specifically to manage.

  • What tooling problem does Google's 'Large-Scale Change' (LSC) process specifically exist to solve, and why can't a normal PR workflow handle it?
    It exists to make a single breaking API change land atomically across every one of its consumers in one commit, even when that spans tens of thousands of files owned by hundreds of different teams. A normal PR workflow assumes one reviewer, one small diff, and one team's context, which doesn't scale to a change that mechanically touches unrelated codeowners everywhere -- LSC tooling auto-generates the mechanical fixes, routes review to the relevant owners in parallel, and lands the whole thing as one atomic commit so the repo is never inconsistent.
  • In a polyrepo, why doesn't a security patch to a widely-used shared library reach all consumers automatically the way it would in a monorepo?
    Because each consumer pins a specific version in its own manifest and only picks up a new version when it explicitly upgrades that pin -- publishing a patched version doesn't change anyone's installed version by itself. Without automated dependency-bump tooling opening upgrade PRs, or an organizational mandate to upgrade promptly, consumers can stay on the vulnerable version indefinitely.

Monorepo coupling is like renovating a house where every room shares the same load-bearing wall -- move the wall and you must fix every room in the same afternoon. Polyrepo coupling is like renting out separate apartments in the same building -- you can renovate one unit's kitchen on its own timeline, but now two tenants might be living with two different kitchen layouts for months.

saying these in an interview costs you the question

  • thinks a monorepo eliminates the need to ever version an API
  • believes 'atomic commit across consumers' is easy without any specialized tooling at scale
  • can't explain why semver exists in a polyrepo context
  • doesn't recognize that loose coupling defers cost to the maintainer supporting old versions

context