A full build takes eleven minutes. A one-line change to one file takes eleven minutes, because the build system rebuilds everything.
Most of that work is recomputing outputs from inputs that did not change. Incremental builds skip it — and only work if the system can determine reliably what actually changed, which is harder than it sounds.
What changed in 2026
- Remote caching became standard. Sharing build results across a team and continuous integration moved from advanced to expected.
- Content-based invalidation displaced timestamps. Hashing inputs rather than comparing modification times became the norm.
- Deterministic builds got attention. Reproducibility became recognised as a prerequisite for effective caching rather than a separate concern.
- Correctness tooling improved. Detecting undeclared dependencies became more practical.
Timestamps versus content
The older approach compares modification times: if a source file is newer than its output, rebuild.
It is cheap and wrong in both directions. Checking out a branch updates timestamps on files whose content is identical, triggering unnecessary rebuilds. And a file restored from a backup or copied with preserved timestamps can be newer in content and older by timestamp, so a needed rebuild is skipped.
Content hashing computes a hash of every input — source files, compiler flags, tool versions, environment — and rebuilds when the hash changes. Correct in both directions, and the hash also becomes a cache key.
|
Timestamps |
Content hashing |
| Branch switching |
Spurious rebuilds |
No rebuild if content matches |
| Restored files |
Can miss rebuilds |
Correct |
| Enables caching |
No |
Yes — the hash is the key |
| Cost |
Trivial |
Reading and hashing inputs |
Determinism enables caching
If a build produces different output from identical input, the output cannot be reused.
The usual sources of non-determinism are avoidable and pervasive: embedded build timestamps, absolute paths that differ per machine, non-deterministic ordering in archives or generated code, and embedded hostnames or usernames.
Each means two people building identical source get different artefacts, so nothing caches and every build is a full build.
Making builds deterministic — fixed timestamps, relative paths, sorted ordering, no environment leakage — is the prerequisite for caching to work at all. It also gives you reproducibility, which is valuable independently for security and debugging.
Remote caching multiplies it
Local incremental builds help one person. A shared cache helps everyone.
When a build result is stored under its input hash in a shared cache, anyone building the same inputs downloads the result instead of computing it. A developer pulling the main branch downloads what continuous integration already built. Continuous integration itself skips work another job already did.
That changes the economics substantially: the marginal cost of building shared code approaches zero, and the only real work is what an individual changed.
The requirements are determinism — otherwise cache keys never match — and complete dependency declaration, so that a cache hit is genuinely valid.
Incorrect incrementality is the real danger
The failure mode that makes people distrust build systems.
If a dependency is not declared, the build system does not know to rebuild when it changes. It reuses a stale artefact, and the resulting binary contains old code with new code — producing behaviour that matches no version of your source, cannot be reproduced by anyone who did a clean build, and disappears when someone clears the cache.
That class of bug consumes enormous time and destroys trust in the build. Once developers start running clean builds "just to be sure", the incremental system is providing nothing.
Two protections. Sandboxed execution, where a build step can only access declared inputs, turns an undeclared dependency into an immediate failure rather than a silent staleness. And periodic clean builds in continuous integration, compared against incremental ones, catch discrepancies before they reach developers.
The principle worth holding: a slow correct build beats a fast build you cannot trust.
Common mistakes
- Timestamp-based invalidation. Wrong in both directions.
- Non-deterministic outputs. Caching cannot work.
- Undeclared dependencies. Silent staleness.
- No sandboxing. Undeclared dependencies stay hidden.
- Never doing a clean build. Drift goes undetected.
- Caching without verifying correctness. Fast and wrong.
- Over-broad dependency declarations. Everything rebuilds on any change; correct and useless.
FAQ
How much faster should builds get?
Depends on how much of your build is genuinely affected by a typical change. A well-configured system on a large codebase frequently reduces a typical change to a small fraction of a full build.
Is remote caching safe?
It requires trusting the cache contents, so signing and access control matter. A compromised build cache is a supply chain problem — see dependency confusion attacks.
What about test results?
The same principle applies — tests whose inputs did not change need not re-run. This is frequently a larger saving than compilation.
How do I find undeclared dependencies?
Sandboxing is the systematic answer. Failing that, clean builds compared against incremental ones will surface discrepancies eventually.
Where to go next
For supply chain risk in build inputs, read dependency confusion attacks. For the broader pipeline, CI/CD best practices and CI/CD caching strategies.