The 2026 Monorepo Scale Benchmark
How CI time grows with repo size, why affected-target builds change the curve, and how cache hit rate and the runner layer decide whether incremental CI actually feels incremental.
Executive summary
Monorepos trade many small CI problems for one large one. Putting every project behind a single build graph is what makes atomic cross-cutting changes possible: you can refactor an interface and update every caller in one commit, with one review and one CI run that validates the whole thing together. That is a genuine engineering advantage, and it is why large organizations adopt the monorepo despite the CI challenge it creates. The challenge is that a naive pipeline rebuilds and retests the entire repository on every push.
As the repository grows, that naive path scales linearly into the kind of CI time that makes engineers dread merging. A monorepo that was tolerable at a few hundred build targets becomes a near-hour gate at a few thousand, not because any individual build got slower but because every push pays for the whole repository regardless of what it changed. The build-everything curve is the reason teams abandon naive monorepo CI long before they ever consider abandoning the monorepo itself.
This benchmark contrasts the two regimes directly. Building and testing everything scales with total repo size and quickly becomes untenable. Building only the targets a change affects scales with the change rather than the repo, which is why a well-instrumented monorepo can stay fast at a size where build-everything would take an hour. The gap between those two curves is the single most important number in monorepo CI, and on a large repository it is more than an order of magnitude.
Affected-target selection only delivers on that promise if the runner layer cooperates. Because the affected set is small, its build is dominated by remote cache hits and runner startup rather than by raw compute, which inverts where the time goes. On a slow build-everything path, compute dominates and startup is noise. On a fast affected-only path, a slow cold start or a cold cache becomes a visible fraction of total time. A warm pool and a fast shared cache are what decide whether incremental CI actually feels incremental.
This report quantifies both curves and the cache and runner factors that bend them, and it follows the cost as well as the clock. The per-push cost on a large monorepo drops sharply as a team moves from build-everything to affected-only to affected-only with a warm cache, and a managed runner layer takes it further by keeping the cache warm and the pool right-sized. The result is that a large monorepo does not have to carry a large CI bill, provided the build strategy and the runner layer are chosen together rather than separately.
Modeled full-pipeline minutes when every target is built and tested, by repo size. · Source: Latchkey analysis (modeled)
Modeled split of wall-clock time for an affected-target build on a large monorepo. · Source: Latchkey analysis (modeled)
Email me the report
The full report is right here on this page, free. Want the link in your inbox to read later or share, plus new Latchkey reports as they drop? Drop your email and we will send it over.
Sent! Check your inbox for the report link.
No spam. Unsubscribe anytime.
Build-everything scales linearly into pain
When CI rebuilds and retests the whole repository on every push, pipeline time tracks total target count almost linearly. The modeled curve makes this concrete: a hundred-target repo validates in single-digit minutes, five hundred targets push past half an hour, and two thousand targets approach an hour and a half. Nothing about the individual builds changed; there are simply more of them, and a naive pipeline runs all of them every time regardless of what the change touched.
That linear curve is the reason teams abandon naive monorepo CI, and they abandon it long before they abandon the monorepo. The monorepo's benefits, atomic cross-cutting changes and a single coherent build graph, remain just as valuable at two thousand targets as at one hundred. What becomes untenable is paying for the whole graph on every push, which turns a near-hour validation into a tax on every merge and discourages the small, frequent changes that healthy engineering depends on.
The build-everything bar sitting next to the affected-only bar on the same chart is the whole argument in one image. The same large monorepo that takes most of an hour to build whole finishes an affected build in single-digit minutes. That contrast is not a marginal improvement; it is the difference between a CI gate that paces development and one that gets out of the way, and it is available without changing the monorepo structure at all.
Affected-only builds scale with the change, not the repo
A median pull request in a large monorepo touches only a small fraction of targets, modeled here at around six percent. Building just the affected set, the targets that actually depend on what changed, decouples CI time from repo size entirely. The pipeline does work proportional to the diff rather than proportional to the repository, which is the entire value proposition of build-graph tooling that understands the dependency graph.
The effect on the clock is dramatic. The same two-thousand-target monorepo that takes most of an hour to build whole can finish an affected build in single-digit minutes, an order-of-magnitude reduction achieved purely by not rebuilding things the change could not have affected. The build graph already encodes which targets depend on which, so computing the affected set is fast, and skipping everything outside it is free.
There is a subtle shift in where the time goes once you make this move, and it sets up the rest of the report. On the slow build-everything path, compute dominates so completely that everything else is noise. On the fast affected-only path, the build is short enough that the supporting costs, cache retrieval, runner startup, and graph calculation, become a visible share of the total. The affected-build split chart shows this new breakdown, and it is what makes the cache and runner factors decisive.
Remote cache hit rate is the dominant lever
Because the affected set is small, an affected build's time is dominated by what comes from cache versus what has to be recomputed. A target that depends on the change must be rebuilt, but a target that the change did not affect can be restored from a remote cache instead of compiled again, and on a healthy monorepo most of the dependency closure is unchanged. The hit rate against that remote cache is therefore the variable that most controls how long an affected build actually takes.
The cache-hit chart shows the leverage directly. The same affected build that takes seventeen minutes against a cold cache drops to around five minutes at a seventy-two percent hit rate and to a few minutes at ninety percent. That is a more-than-fivefold swing driven entirely by cache behavior, with the affected set held constant. Cache infrastructure is the highest-leverage investment in a mature monorepo precisely because it moves this number, and the number moves the whole build.
Getting the hit rate up is operationally finicky in the familiar way. Cache keys scoped too loosely serve stale artifacts and produce wrong builds; keys scoped too tightly almost never hit and waste the cache entirely. The teams that win treat cache keys as a first-class part of the pipeline, scoped to inputs and toolchain versions, and they monitor hit rate the way they monitor test coverage, because a cache nobody measures is a cache that silently stops working and quietly returns the build to the cold-cache column.
- On a healthy monorepo most of an affected build's dependency closure is unchanged and can be restored from cache.
- The modeled affected build swings from seventeen minutes cold to a few minutes at a high warm-cache hit rate.
- Scope cache keys to inputs and toolchain versions, and monitor hit rate the way you monitor test coverage.
Modeled minutes for the same affected build as the warm-cache hit rate rises. · Source: Latchkey analysis (modeled)
Runner startup is a bigger share when builds are short
Once an affected build is only a few minutes long, the fixed costs that were invisible on the build-everything path become a meaningful fraction of the total. A cold start that takes thirty to sixty seconds to provision a runner is a rounding error against a ninety-minute build, but it is a large slice of a three-minute one. The affected-build split shows runner startup and graph calculation taking a double-digit share precisely because the build it sits inside is now fast.
A warm pool is what keeps that slice small. When a runner is already provisioned and waiting, the build starts in seconds rather than waiting on a cold instance to boot, attach, and pull its image. For the slow build-everything path this barely mattered, since startup was a tiny fraction of a long run. For the fast incremental path it matters a great deal, because shaving thirty seconds of cold start off a three-minute build is a ten-to-twenty percent improvement in the metric developers actually feel.
This is the point where the build strategy and the runner layer stop being separable. Affected-target selection makes the build short, which makes startup latency visible, which makes a warm pool valuable. A team that adopts incremental builds but leaves a cold-start runner layer underneath captures most of the compute savings while leaving a visible chunk of wall-clock time on the table, and the developers feel the cold start on every otherwise-fast build. The cost chart shows the same coupling in dollars.
Modeled CI cost for one push under each build strategy. · Source: GitHub Actions pricing + Latchkey analysis (modeled)
Per-push cost falls fastest when strategy and runners combine
The cost-per-push chart traces the same progression as the time charts but in dollars, and the drop is steep. Building everything on a two-thousand-target monorepo costs around seventy cents per push at published hosted Linux rates. Switching to affected-only with a cold cache cuts that by roughly four-fifths, because most of the repository is no longer being built. The strategy change alone, before any cache or runner optimization, is the largest single step.
Warming the cache compounds it. An affected-only build served largely from a warm remote cache drops the per-push cost again, because the targets that would have been recompiled are restored instead, and restoration is near-free compared to compilation. By this point the per-push cost is a small fraction of the build-everything figure, achieved entirely through build strategy and cache behavior without changing the runner layer at all.
The managed-runner step takes it the rest of the way. Keeping the cache warm and the pool right-sized, a managed layer lands the per-push cost lower still, roughly 70% below hosted cost on the minutes that remain, so a large monorepo does not carry a large CI bill. The chart's left-to-right descent is the report's thesis in one figure: a large monorepo's CI cost is a function of how it is built and where the runners come from, not of how large it is.
- Affected-only with a cold cache is the largest single cost step, cutting per-push cost by roughly four-fifths.
- A warm remote cache compounds the saving by restoring instead of recompiling unchanged targets.
- A managed runner layer lands the remaining minutes roughly 70% below hosted cost.
Affected-target selection is only as good as the build graph
The entire incremental-CI story rests on knowing precisely which targets a change affects, and that knowledge comes from an accurate build graph. If the graph under-declares dependencies, the affected set is too small and CI passes a change that actually broke something downstream, which is a correctness failure far worse than a slow build. If the graph over-declares, the affected set is too large and the team loses much of the speed advantage to phantom dependencies.
This is why monorepo teams invest in build-tool hygiene as a prerequisite, not an afterthought. Explicit, accurate dependency declarations, hermetic build steps that do not reach outside their declared inputs, and tooling that fails loudly on undeclared dependencies are what make the affected set trustworthy. A monorepo with a sloppy build graph cannot safely run affected-only CI, so it is stuck on the expensive build-everything path until the graph is cleaned up.
The practical sequence for a team adopting incremental monorepo CI is therefore graph first, then strategy, then runners. Get the dependency graph accurate enough to trust the affected set, switch CI to build only that set, then warm the cache and right-size the runner layer so the now-short builds feel short. Skipping the first step does not just slow things down; it risks shipping breakage, which is the one outcome a CI gate exists to prevent.
Test selection mirrors build selection, and both need isolation
Affected-target logic applies to testing as directly as it applies to building. There is little value in compiling only the affected targets and then running the entire test suite, since the test run would re-impose the build-everything cost on the validation side. The same dependency graph that selects which targets to compile can select which tests are reachable from the change, so testing scales with the diff just as building does.
The affected-build split reflects this: compiling and testing the affected targets together make up the bulk of the wall-clock time, and both are proportional to the change rather than the repo. The testing slice is meaningful but bounded, because only the tests that exercise affected code need to run. A team that selects affected builds but runs all tests has solved half the problem and left the more expensive half in place.
Isolation ties the testing story back to reliability. A clean, single-use environment per job removes the state-bleed flakiness where one test contaminates another across runs, which is especially valuable in a monorepo where many targets share a runner. Managed runners that provision a fresh environment per job deliver that isolation by default, so the affected tests run in a predictable environment and a transient failure can be recovered without re-running the whole affected build from the top.
Recommendations
Get the build graph accurate before anything else
Affected-target selection is only safe if the dependency graph is accurate. Under-declared dependencies cause CI to miss real breakage; over-declared ones inflate the affected set and waste the speed advantage. Invest in explicit dependency declarations, hermetic build steps, and tooling that fails on undeclared dependencies before switching CI to incremental, or you risk shipping breakage.
Switch CI to build and test only affected targets
Building everything scales linearly into a near-hour gate at a few thousand targets. Building only the affected set decouples CI time from repo size and is the largest single step in both time and cost. Apply the same affected-set logic to testing, not just building, or you re-impose the build-everything cost on the validation side.
Treat the remote cache as the highest-leverage investment
On a short affected build, cache hit rate is the dominant lever, swinging the modeled build from seventeen minutes cold to a few minutes warm. Scope cache keys to inputs and toolchain versions, and monitor hit rate like test coverage. A cache nobody measures silently returns the build to the cold-cache column.
Put a warm pool under the now-short builds
Once affected builds are only a few minutes, cold-start latency becomes a visible double-digit share of total time. A warm pool that starts in seconds keeps that slice small, which matters far more for fast incremental builds than it ever did for the slow build-everything path it replaced.
Use a managed runner layer to keep a large monorepo's bill small
A managed layer keeps the cache warm and the pool right-sized, landing the remaining per-push minutes roughly 70% below hosted cost. Combined with affected-target selection and a warm cache, it makes a large monorepo's CI cost a function of how it is built and sourced, not of how large it is.
Outlook
Expect the gap between graph-aware monorepo teams and naive ones to keep widening through 2026 and into 2027. The optimizations in this report compound: an accurate graph enables affected-only builds, which make the build short enough that cache hit rate and warm pools dominate, which a managed runner layer then keeps tuned. A team with all of these in place stays fast and cheap as the repo grows; a team with none of them feels the linear build-everything curve steepen with every quarter of growth.
Build-tool and caching ecosystems will keep maturing toward making affected-target CI the default rather than an expert configuration, which lowers the barrier for teams that adopt the monorepo but have not yet invested in graph hygiene. As that happens, the differentiator shifts from whether a team can do incremental CI at all to whether the runner layer underneath is fast enough to make the now-short builds feel instant, which is exactly where warm pools and managed runners earn their keep.
For most monorepo teams the practical takeaway is that repository size is not destiny for CI time or cost. The build-everything curve is a choice, not a law, and the levers that bend it, an accurate graph, affected-target selection, a warm remote cache, and a right-sized warm runner pool, are well understood and compounding. The organizations that adopt them treat the monorepo's coherence as a pure advantage while their CI stays fast, and the ones that do not keep paying for the whole repository on every one-line change.
Methodology
This benchmark contrasts build-everything CI time, modeled as roughly linear in target count, with affected-only CI time, modeled as a function of the affected set and remote cache hit rate rather than repo size. Affected-set share, cache hit rates, and time splits are illustrative Latchkey estimates derived from typical monorepo build graphs, not a primary survey. Per-push cost figures combine modeled runner-minutes with published GitHub-hosted Linux rates ($0.008/min). Modeled figures are intended to show direction and magnitude rather than a precise population value. Figures labeled "modeled" are illustrative estimates derived from public pricing and typical pipeline shapes, not a primary survey; figures attributed to a named source reflect that source. Pricing reflects published rates at time of writing and should be verified against current provider pricing.