How do you speed up and diagnose slow builds: incremental builds, whole-module optimisation, explicit modules and the build timeline?
Predict first
A clean build is slow and the module graph is a chain of ten modules that each depend on the previous one. What limits the speed?
// A: the number of CPU cores, since all modules build in parallel
// B: the critical path: each module waits for the one before it, so extra cores do not help
// C: the size of the Package.swift file
- A
- B
- C
Mental model. Speeding up builds is like tuning a kitchen: first time how long each station takes, then fix the slowest (one dish with an impossible recipe), and cache prepped ingredients so the next service does not start from zero.
First measure. Build time has distinct parts: dependency resolution, compiling each module’s sources, linking, code signing, asset and resource processing, and run-script phases. Xcode’s Build Timing Summary (Product ▸ Perform Action ▸ Build with Timing Summary), the build log’s timeline, and the compiler flags -Xfrontend -warn-long-function-bodies=100 and -warn-long-expression-type-checking=100 (which warn on bodies or expressions that take more than that many milliseconds to type-check) show where the time goes. Typical culprits: a few pathological expressions (long operator chains, big literal arrays and dictionaries without type annotations, heavy result-builder bodies), too few or too many modules, a shared hub module that invalidates everything, a slow script phase that runs every build, and clean builds on CI with no caching.
The levers. Incremental builds: Debug compiles only the files affected by a change (the compiler tracks dependencies between declarations); keep them effective by making interfaces small and stable, avoiding huge files and avoiding changes to widely used headers or “core” declarations. Whole-module optimisation (WMO), used in Release, compiles a module as one unit for better cross-file optimisation (specialisation and devirtualisation) at the cost of recompiling the whole module on any change, so Debug stays incremental and per-file. Modularisation lets unchanged modules skip work, but only if the graph is shallow and hubs are stable (see the module graph question). Explicit modules (Xcode 16: a dependency-scanning step that builds module dependencies as separate, cacheable tasks) make builds more parallel and deterministic. Compilation caching (a content-addressed cache shared between machines, such as the compiler cache in recent Xcode or distributed caches in Bazel and Tuist) avoids repeating identical work on CI. Static linking of internal modules, fewer dynamic frameworks, and mergeable libraries reduce link time and launch cost. Previews slow down when the previewed target depends on a large graph: give features a small preview host or demo app. Also: type-annotate to help the type checker, split giant files and expressions, turn off unneeded debug-info generation for dependencies (DEBUG_INFORMATION_FORMAT), keep script phases incremental by declaring inputs and outputs, and use a fast machine and SSD. Change one thing at a time and record before and after, because build-time folklore is full of tips that do nothing on your project.
A module cannot start until its dependencies are built, so a long chain serialises the build. Flattening the graph is what lets cores help.
Common traps
- Optimising by folklore without measuring. The build timeline and long-type-check warnings show the real hot spots first.
- Splitting into many modules without watching the graph. A deep chain or a hub module can make builds slower, not faster.
They might follow up with
- Why is Release built in whole-module mode and Debug incrementally? WMO optimises across files but rebuilds the module on any change; incremental favours quick edit-build cycles.
- How do you find slow type-checking? The
-warn-long-expression-type-checkingand-warn-long-function-bodiesfrontend flags.
Related questions
- 16.3.2 Why must the module dependency graph be acyclic, how do you layer it, and what does its shape do to build…
- 16.4.1 What are build configurations and build settings, how do xcconfig files layer, and how does #if DEBUG work?
- 16.6.1 A CI pipeline takes hours on a large modular app. How do you analyse and cut it: affected builds, caching…
← What are build configurations and build settings, how do xcconfig…