← Blog

Engineering

The Hidden Tax Every Engineering Team Is Paying (And Doesn’t Talk About)

8 min read

There’s a number almost no engineering org tracks: how many collective hours their developers spend staring at a build progress bar every week.

Not writing code. Not designing systems. Not shipping features. Just… waiting.

If your team runs 30–40 builds a day, and each one takes 50 minutes instead of 15, you’re not losing 35 minutes. You’re losing 35 minutes multiplied by every engineer, every build, every day, compounding into months of lost engineering time a year. And unlike a production outage, nobody files an incident report for a slow build. It just quietly taxes your velocity forever, until someone decides to fix it.

Build optimization doesn’t get the attention it deserves because it doesn’t look like a “real” engineering problem. It’s not a new feature. It’s not a scaling milestone. It’s plumbing. But plumbing determines how fast everything else in the house works.

CI/CD is a loop — every slow stage compounds forever
CI/CD is a loop — every slow stage compounds forever

The Two Costs Nobody Puts on a Slide

Compute is the visible bill — context switching is the invisible one
Compute is the visible bill — context switching is the invisible one

1. The direct cost — compute

Every build consumes CPU, memory, and often paid CI minutes. A bloated pipeline running redundant steps, rebuilding unchanged modules, or spinning up oversized runners is literally burning money on infrastructure that produces nothing. At scale (hundreds of builds a day across a growing team), this adds up to a real, visible line item. Companies routinely discover they’re paying for 3–4x more compute than the actual work requires, simply because nobody audited the pipeline since it was first written.

2. The invisible cost — context switching

This one is more expensive, and almost nobody measures it. When a build takes 40–50+ minutes, developers don’t sit and watch it. They switch tasks, check Slack, open a different ticket, and then pay a “resumption tax” when they come back to reload the mental model of what they were doing. Research on developer flow state consistently shows that switching costs far exceed the wait time itself. A 50-minute build isn’t a 50-minute cost. It’s often far more once you account for how long it takes to get back into deep focus, and how many times that interruption happens in a day.

Multiply that across a team, across a year, and slow builds become one of the largest hidden drains on engineering productivity, bigger than most teams’ entire “developer experience” budget.

Why This Gets Worse as You Scale, Not Better

Early on, builds are fast because codebases are small. Nobody notices the problem until it’s already expensive to fix. As a codebase grows:

  • Dependency graphs get tangled, and clean modularization erodes
  • More tests get added without a parallelization strategy
  • CI pipelines accumulate steps nobody remembers the purpose of
  • Caching layers are either missing or invalidated too aggressively to help
  • Generated artifacts creep into version control and become everyone’s problem

In a monorepo, this compounds faster. One slow path isn’t one team’s problem. It’s every team’s wait time, every PR, every day. A redundant step in a shared pipeline taxes backend, frontend, and platform engineers alike. A bad cache key or an unbalanced test shard shows up as “CI is slow” for the whole org, not one squad.

One shared pipeline — every team pays the same tax
One shared pipeline — every team pays the same tax

The result is a slow, creeping decline that’s easy to rationalize away one sprint at a time (“it’s just a bit slower this quarter”) until a team wakes up to 40–50 minute builds and wonders how they got there.

There’s a related trap that looks like progress: throwing more machines at it. Parallelization helps. But parallelizing work you shouldn’t be doing at all has a cost curve that goes the wrong way. You can finish earlier and spend more.

What Actually Moves the Needle

Not all optimizations are equal. A small set of principles accounts for most of the gains:

Only rebuild what changed. Incremental builds sound obvious, but a shocking number of pipelines still rebuild the world on every commit because incremental support was never configured, or was configured once and quietly broken.

Cache for correctness first, speed second. Shared caching is where real gains show up. The hard part isn’t turning a cache on. It’s versioning it so it doesn’t lie across branches, toolchains, or parallel writers.

Parallelize the right work, then balance it. A tangled “everything depends on everything” graph makes parallelization impossible no matter what tooling you buy. And even when you fan out, unbalanced shards leave agents idle while the slowest one owns wall-clock. “Parallel” is not the same as “balanced.”

Delete ceremony. Decouple prove from ship. The same lint or type-check often runs in the PR gate and again in the deploy build. Unit tests rarely need a full production packaging step. If your feedback loop waits on work that isn’t required for that job’s purpose, you’re paying a coupling tax.

Stop treating humans as a build step. Unused dependencies, oversized artifacts, and committed generated output silently slow builds, reviews, and merges. Hygiene is unglamorous and pays for itself repeatedly.

None of this is exotic. The reason it doesn’t get fixed isn’t lack of solutions. It’s lack of ownership. Build performance sits in the gap between “not really infra’s job” and “not really product engineering’s job,” so it drifts.

How to Actually Achieve It

Knowing the principles isn’t enough. Order matters, especially in a monorepo where “fix the build” can feel too big to start.

1. Instrument before you optimize

Break the pipeline into stages and measure wall-clock: dependency restore, compile/package, lint, unit tests (per shard), integration/e2e, artifact publish. Find the slowest stage and the widest gap between shards. Those two numbers tell you where to go first.

2. Draw the dependency graph of the pipeline

For every job, ask: does this step need to succeed for this job’s purpose? PR gates need correctness signals. Deploy builds need shippable artifacts. Local loops need the shortest path to “is my change broken?” Anything that appears twice without a reason is a deletion candidate.

3. Delete before you parallelize

This is the sequence that consistently pays off:

  1. Remove duplicate gates
  2. Decouple test from ship
  3. Stop committing generated artifacts
  4. Fix cache correctness (version by branch/lockfile/toolchain; isolate parallel writers)
  5. Replace the slow tool on the hot path
  6. Parallelize the bottleneck itself (a single-threaded step on a 16-core box is not fixed by more CI agents)
  7. Rebalance shards (idle agents are still cost)
  8. Only then buy more machines

If you reverse this order, you often get a faster and more expensive pipeline.

4. Ship it as a portfolio, not a hero PR

One dramatic change rarely delivers a lasting win. A sequence of small, measured improvements does. Track before/after per stage so the org doesn’t forget and reintroduce the fat.

5. Assign an owner

Someone has to be accountable for pipeline wall-clock and CI cost the way someone owns latency SLOs. Without that, every sprint prefers a product ticket over plumbing, and the tax keeps compounding.

How I Did It in a Monorepo

I own this story from inside a large production monorepo: many modules, shared libraries, multiple CI paths, and a build that had grown the way monorepo builds usually do, by accretion.

The starting point looked familiar. Packaging paths that redid far more work than they needed to. A custom asset-processing step that didn’t scale with available cores. Caches that existed but weren’t versioned or isolated safely for parallel workers. Quality checks running twice, once as a PR gate and again in the deploy path. Unit tests gated behind a full production-style build they didn’t need. Test shards that were “parallel” but uneven. Generated artifacts committed to git (on the order of ~150K+ lines), creating noisy diffs, merge conflicts, and a manual rebuild ritual.

None of that is unique to one language or framework. It’s what happens when a monorepo’s shared pipeline is everyone’s infrastructure and nobody’s roadmap item.

I worked the sequence above. Made the bottleneck itself parallel instead of only fanning the same slow step across more agents. Cached correctly, not just “on,” with branch-based versioning and per-worker cache directories. Deleted duplicate static analysis and decoupled unit tests from the ship build. Rebalanced uneven test shards so agents finished closer together. Moved generated artifacts into the pipeline and out of git. Replaced a CPU-heavy step on the hot path with a much faster tool. Cleaned the long tail: unused dependencies, LTS upgrades, better dependency caching, sensible test timeouts.

What didn’t work was also useful. A popular “rewrite the compiler stack for speed” migration looked great on paper but failed the risk/reward test under our framework constraints. Documenting why a trendy optimization doesn’t apply saves the next engineer weeks.

Results on the paths I owned:

  • ~10–15 minutes faster on the first major build path
  • ~19–20 minutes faster on the second
  • ~32 minutes of wall-clock saved combined
  • ~50% cost reduction on those builds, because we removed work, not only parallelized it
  • ~150K+ LOC of generated artifacts out of version control

The monorepo lesson that stuck with me: optimizations in a shared pipeline are force multipliers. One deleted step, one correct cache, one balanced shard, and every team that ships through that pipeline inherits the win. That’s also why ownership matters more here than in a single-service repo. If nobody owns the shared path, everybody pays.

The Business Case, Not Just the Engineering Case

If you’re trying to get this prioritized, don’t pitch it as a developer comfort issue. Pitch it as what it actually is: a cost and velocity issue.

Cost: Reduced CI compute time translates directly into lower cloud spend, often the single easiest infrastructure cost to cut with zero product risk.

Velocity: Faster feedback loops mean faster iteration, faster bug fixes, and faster time-to-production, which compounds across every team that ships through that pipeline.

Retention: Developer experience is a real retention lever. Engineers notice when their tools work against them, and “our build is painfully slow” is a recurring theme in exit interviews at companies that never invested here.

Framed this way, build optimization stops looking like a nice-to-have cleanup task and starts looking like what it is: one of the highest-leverage, lowest-risk investments an engineering org can make. You’re not betting on an unproven feature. You’re removing friction from something every single engineer touches, every single day.

The Real Question

The question isn’t whether your build pipeline has room to improve. Almost every pipeline does, because nobody budgets time to maintain it the way they budget time to build features. The real question is whether anyone owns it.

If the honest answer is “not really,” that’s usually the actual problem worth solving first, especially in a monorepo, where the tax is shared, and the ownership gap is largest.

What’s the slowest part of your build pipeline right now, and has anyone actually looked at why?