How does incremental compilation decide what to rebuild, and why does a small edit sometimes rebuild half the app?

5.6.2 · L5 · Interview

Predict first

You change only the body of a private function, not its signature. What gets recompiled?

// A: every file in the module
// B: only the file containing it
// C: the whole app including dependencies
  • A
  • B
  • C

Predict first

Which edit is most likely to rebuild a large part of the project?

// A: changing a comment
// B: adding a requirement to a protocol used throughout the app
// C: editing a function body
  • A
  • B
  • C

Mental model. A spreadsheet recalculates only the cells that depend on the one you changed. Edit a cell everyone references and half the sheet recalculates.

The compiler records, for each file, what it provides (the declarations it defines) and what it depends on (the declarations it uses, by name). After an edit it compares the file’s interface with the old one. If only a function body changed, only that file recompiles, because nothing else could be affected. If an interface changed (a signature, a stored property, a protocol requirement, an @inlinable body, a typealias, a conformance), every file that depends on the changed name is rebuilt, and then their interface changes are checked in turn.

So a small edit explodes when the changed declaration is widely used: a core model type, a protocol with many conformers, a big extension hub, or a shared typealias. Editing the bridging header invalidates every Swift file that sees it. To keep changes local, keep types small and focused, use private and fileprivate so dependencies stay narrow, split a large module into smaller ones along change frequency, and depend on narrow protocols at module boundaries. Remember that WMO builds, used for Release, give up incrementality entirely.

The file’s interface is unchanged, so no other file can be affected. Only that file is recompiled.

A protocol requirement changes an interface that every conforming and using file depends on, so all of them must be rechecked.

Common traps

  • Putting everything in one giant extension or shared utilities file. Every file depends on it, so every change to it rebuilds everyone.
  • Editing the bridging header casually. It fans out to every Swift file in the target.

They might follow up with

  • How do you see what it rebuilt? -driver-show-incremental prints the reason each file was scheduled.
  • Does a change inside a function body ever rebuild others? Not through the interface, unless the function is @inlinable or its signature changed.

Related questions

  • 5.6.1 A Swift file takes forever to compile. How do you find the cause and fix it?
  • 5.2.2 What does whole-module optimisation change, and what does it cost?
  • 4.8.1 How do Swift and Objective-C see each other: bridging header, umbrella header, and the -Swift.h file?

← A Swift file takes forever to compile. How do you find the cause and…