advancedBuilds
How does a build system know what to recompile after a one-line change?
Whether the candidate can reason about dependency granularity and correctness, not just timestamps. The discriminator is whether they raise the two failure directions — rebuilding too much and rebuilding too little — and know which one is dangerous.
What a strong answer covers
- At the coarse level: a dependency graph over compilation units, plus a signal that an input changed. Timestamps are the classic signal and are wrong in both directions — a restored file has an old timestamp, a touched file has a new one with identical contents — so serious systems hash content instead, and hash the compiler version and flags along with it, because a flag change invalidates everything.
- The interesting question is granularity. If a change to any header invalidates every unit that includes it, adding a comment to a widely included header rebuilds the world. So systems try to depend on interfaces rather than files: an interface file or module summary that changes only when the public shape changes, so a change to a function body invalidates the units that inline it and nothing else. Rust's query system takes this further, memoizing per-query results and re-running only the queries whose inputs actually changed, so an edit to a function body can leave type-checking results for the rest of the crate intact.
- The two failure directions are asymmetric. Rebuilding too much wastes time and is self-correcting. Rebuilding too little produces a binary containing stale objects compiled against an old declaration — an ABI mismatch with no error, which is the same silent corruption as any other ABI break. That asymmetry is why build systems are conservative by default and why "clean build fixes it" is folklore that survives.
- The other half is hermeticity. If compilation depends on anything not in the graph — an environment variable, a system header, a timestamp baked into the output, the absolute path of the build directory — then the cache is unsound and remote caching or shared caching will hand someone a wrong artifact. Reproducible builds and incremental builds are the same discipline seen from two angles.
✓ Green flags
- Prefers content hashing to timestamps and says why.
- Raises granularity and interface-versus-implementation dependencies.
- States the asymmetry: under-building is a correctness bug, over-building is a performance bug.
- Mentions that flags and compiler version are inputs.
- Connects incrementality to hermeticity and cache soundness.
✗ Red flags
- "It checks the timestamps." The starting point, and it is wrong in both directions on any machine with a checkout, a cache or a clock.
- "Just rebuild everything, it is safer." It is safer and it is why some codebases have a forty-minute edit-test loop; the engineering is in making it correct *and* small.
- "Incremental builds are unreliable, that is why we clean." Unreliability is a bug in the dependency graph, and treating it as inevitable removes the pressure to fix it.
- "Header changes only affect files that use the changed function." Textual inclusion means the unit depends on the whole header unless the language has real modules.
Follow-up
Someone reports that a clean build produces a different binary than an incremental one. What are the candidate causes, in the order you would check them?
Implementation challenge
What to ask them to write or trace on a whiteboard.
Given four source files and their include graph, list the exact set to rebuild after a change to one function body, and then after a change to that function's signature.