What Really Happens When You Press Command+B (And Why It Takes Forever)
Muralidharan Kathiresan
But here's the thing nobody tells you: most developers have no idea what's actually happening during a build. We press ⌘B, watch the progress bar, and hope it finishes soon. When it's slow, we just accept it. "That's how Xcode is," we say.
That was me too. Until I got fed up and started digging into the internals.
What I learned changed everything. llbuild (Xcode's low-level build engine) constructs a directed acyclic graph of build tasks, where each node represents a compilation unit and edges represent dependencies. When you change a single Swift file, llbuild walks this graph to determine the minimal set of nodes that need rebuilding. But here's the catch: Swift's module dependency system is more granular than most developers realize. A change to a protocol or an @inlinable function can cascade through dozens of files because of how Swift tracks dependencies at the declaration level through .swiftdeps files.
The Swift compilation pipeline itself has multiple bottleneck points. After parsing and AST construction, the type checker can spend seconds on complex generic constraints or heavily overloaded operators. Then comes SIL (Swift Intermediate Language) generation, where the compiler performs mandatory optimizations before passing to LLVM. Each of these stages can be profiled using compiler diagnostics flags like -Xfrontend -debug-time-compilation and -Xfrontend -debug-time-function-bodies.
I discovered that our "incremental" builds weren't actually incremental: - Protocol changes were invalidating entire dependency chains - Bridging headers were forcing recompilation of Swift files that hadn't changed - Generic specialization was creating hidden compile-time explosions - Module maps weren't being cached properly between CI runs
The Results
- CI build time: 25 minutes → 9 minutes (64% improvement)
- CI infrastructure costs: reduced by 60%
- Developer time recovered: 2+ hours per week
Time they're now spending writing code instead of waiting for builds.
What You'll Learn
Deep Dive into llbuild's Task Execution
How Xcode's build engine constructs and walks dependency graphs
Swift's Compilation Pipeline
Understanding parsing → type checking → SIL → LLVM stages and their bottlenecks
Incremental Build Invalidation Patterns
Why your "incremental" builds might not be incremental at all
Profiling with Compiler Diagnostics
Using -Xfrontend flags to identify exactly what's slow
The Optimizations That Actually Worked
- SPM dependency caching — avoiding redundant resolution
- Whole Module Optimization (WMO) — when to use it, when to avoid it
- Build settings that mattered — the flags that made real differences
The CI/CD Multiplier Effect
Advanced techniques for compounding savings across your team
Audience Level
Senior / Advanced
Muralidharan Kathiresan
Over the years, Murali has built apps, led teams, and shares his experiences with the iOS community through SwiftPublished.in — a weekly blog and YouTube channel he created to share insights, tutorials, and trends in Swift development.
He’s also part of the SwiftLeeds Conference team, helping bring developers together to learn, collaborate, and celebrate the craft of building great apps.
Beyond code, he enjoys mentoring aspiring developers, running workshops, and exploring new ways to make learning iOS development more accessible. For Murali, writing clean, well-tested code isn’t just good practice — it’s a mindset that drives quality and creativity in everything he builds.