Monorepo vs Polyrepo:
The Same Task, Side-by-Side

Five common engineering tasks shown side-by-side in monorepo vs polyrepo. Real command sequences, 2026 tooling references (Nx, Turborepo, GitHub Actions). Each task ends with a verdict.

1. Bump a shared library version

Mono: one PR, one diff, atomic. Poly: publish bump then per-consumer PRs.

Monorepo (Nx or Turborepo)
# shared-lib is in packages/shared-lib
# consumer apps are in apps/app-a, apps/app-b

# Make your change in packages/shared-lib
git add packages/shared-lib/src/utils.ts
git commit -m "feat(shared-lib): add parseConfig util"

# One PR covers the library change
# AND all usages -- no publish step needed
# apps/app-a imports { parseConfig } from '@org/shared-lib'
# No version number to bump. No npm publish.
Polyrepo (separate repos)
# Step 1: bump in shared-lib repo
cd shared-lib
npm version minor  # 2.1.0 -> 2.2.0
git add . && git commit -m "feat: add parseConfig util"
git push origin main
npm publish        # publish to npm registry

# Step 2: open a PR in app-a to consume 2.2.0
cd ../app-a
npm update @org/shared-lib
git add package.json package-lock.json
git commit -m "chore: bump shared-lib to 2.2.0"
# PR, review, merge

# Step 3: repeat for app-b, app-c, ...
# If apps are many, this is a 2-day process
Verdict

Monorepo wins: single atomic PR vs multi-step publish cycle.

2. Rename a function used in 4 packages

Mono: single grep-and-rename PR. Poly: deprecate-publish-update-per-consumer cycle.

Monorepo
# Rename parseUserData -> parseUser across all packages
# In your IDE or with sed/codemod:
npx jscodeshift -t codemod-rename.js packages/

# Or with global search-replace:
grep -rn "parseUserData" packages/ apps/
# Make the change everywhere
git add -A
git commit -m "refactor: rename parseUserData to parseUser across codebase"
# One PR. One review. Atomic.
Polyrepo
# Step 1: deprecate old name in shared-lib
# Add @deprecated JSDoc, export alias
# shared-lib: publish 2.3.0

# Step 2: update each consumer repo in sequence
# app-a: PR to migrate from parseUserData -> parseUser
# app-b: separate PR
# app-c: separate PR
# app-d: separate PR

# Step 3: after all consumers migrated,
# remove deprecated alias in shared-lib 3.0.0

# Total: 5 PRs, 3-4 review cycles, days to weeks
# Risk: some consumers stay on deprecated name indefinitely
Verdict

Monorepo wins: refactor in one commit vs deprecation-publish-update-remove cycle.

3. Add a new shared utility

Mono: import directly. Poly: create package, publish, version-bump consumers.

Monorepo
# Add the util to packages/utils/src/format.ts
export function formatCurrency(n: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency', currency: 'USD'
  }).format(n);
}

# Use it immediately in any app:
# apps/checkout/src/total.ts
import { formatCurrency } from '@org/utils';

# No publish step. No version number.
# Available immediately after the PR merges.
Polyrepo
# Option A: add to existing shared-lib repo
cd shared-lib
# Write the util
npm version patch    # publish 2.3.1
npm publish

# Then in each consumer repo:
npm install @org/shared-lib@2.3.1
git commit -m "chore: bump shared-lib"

# Option B: new package entirely
mkdir format-utils && cd format-utils
npm init -y
# Set up build config, tsconfig, CI, README
# 1-2 hours before you can write the actual util
# Then publish + consumer updates
Verdict

Monorepo wins: shared code is available immediately.

4. Coordinate a breaking API change

Mono: atomic change + build confirmation. Poly: contract test + staged rollout.

Monorepo
# Change the API signature in packages/api-client
# TypeScript catches all consumers at compile time

# Run affected check before merging:
npx nx affected:build --base=origin/main
# ✓ packages/api-client built
# ✓ apps/app-a built (consumer)
# ✓ apps/app-b built (consumer)
# All compile errors resolved in the same PR.

# The build gate is your contract test.
# If it builds, the change is consistent.
Polyrepo
# Step 1: bump API version in api-client repo
# Add new method signature, deprecate old one
# Publish api-client 3.0.0 (breaking)

# Step 2: consumer migration (each repo separately)
# - Update api-client version
# - Find all usages of old API
# - Test
# - PR + review

# Step 3: monitor for runtime failures
# TypeScript might not catch everything across
# separate compilation contexts

# Step 4: deprecate old API version after 90 days
# Publish 4.0.0 removing deprecated methods

# Contract tests (Pact, OpenAPI) help but
# cannot guarantee compile-time safety across repos
Verdict

Monorepo wins: TypeScript compile-time validation across all consumers in one PR.

5. Run CI for a single-service change

Mono: needs 'affected' discipline. Poly: trivially isolated.

Monorepo (with affected builds configured)
# Change in apps/billing only
# With Nx affected or Turbo --filter:

# Nx
npx nx affected:build --base=origin/main
# Runs: apps/billing (changed)
# Skips: apps/checkout, apps/api, packages/*
# (unless billing imports from them)

# Turborepo
npx turbo run build --filter=...[origin/main]
# Same: only billing and its deps

# Without affected configured:
# Full rebuild = all 40 packages rebuild
# This is the monorepo CI anti-pattern
Polyrepo
# Change in billing repo only
# CI is trivially isolated:
# billing's CI pipeline runs
# checkout's CI pipeline: not triggered
# api's CI pipeline: not triggered

# No configuration needed.
# This is the default polyrepo behaviour.

# Trade-off: if billing changes an API that
# checkout depends on, checkout's CI doesn't
# run. You only find the breakage when checkout
# deploys and hits the updated billing API.
Verdict

Polyrepo wins (by default). Monorepo wins with affected + proper caching. Without it, monorepo loses.

What the code samples show

Monorepo wins 4 of 5 tasks by default. The one task where polyrepo wins naturally (single-service CI isolation) is easily solved in a monorepo with affected builds configured. The reverse is not true: the monorepo advantages (atomic commits, compile-time cross-package type checking) cannot be replicated in polyrepo.

The catch: the affected build configuration must be in place. A monorepo without affected builds configured is worse than a polyrepo for task 5.

CI/CD setup →Pick a tool →Decision guide →