The cold-start obsession
Cold start is the first promise an app makes to its user. If it takes longer than a second to become usable, we’ve already taught the person on the other side that this is a slow app, regardless of how fast anything else gets after that.
It’s also the moment people experience most often. A habit tracker gets opened for a few seconds to tick something off. A migraine tracker gets opened when someone feels unwell and wants to log it quickly. For apps like these, launch isn’t a preamble to the experience — it’s most of the experience.
Our target
We aim for under 600 ms from icon tap to interactive UI on a five-year-old iPhone. That’s after the OS has loaded the binary, allocated memory, and given us the main thread. Everything that happens between that handoff and the first frame is on us.
“Interactive” is the important word. A splash screen that appears instantly and then sits there doesn’t count. Neither does a first frame full of placeholders that rearrange themselves a moment later. The target is the point where you can tap the thing you came for and it responds.
We measure on the old device, not the new one, for the same reason we test there at all: the newest phone will hide almost any mistake.
What it forces
Hitting that number ends a lot of arguments early. Networking can’t happen on the launch path. Third-party SDKs are a hard sell — most of them spawn background work before application(_:didFinishLaunching…) even returns. Image caches load lazily. Database migrations run in a background queue, never blocking the main one.
A few habits follow from that. The first screen is built from data already on the device, so the app shows something real immediately and refreshes quietly if anything needs updating. Features that aren’t needed on the first screen don’t get set up until someone asks for them. And whenever we’re tempted to add a library, we ask what it does at launch before we ask what it does for us.
The trade-offs
This discipline isn’t free. Turning down a convenient SDK sometimes means writing and maintaining the equivalent code ourselves. Showing local data first means designing for the case where it’s slightly out of date. Lazy loading means extra care to avoid a stutter the first time a heavier screen opens. We accept those costs because the alternative is a small tax on every single launch, forever, for everyone.
What we got wrong
Launch time rarely breaks in one dramatic change. It erodes: a little setup code here, an extra check there, each harmless on its own. We learned that the hard way, noticing late that an app had become slower to open without any single commit to blame. Now launch time is checked on the reference device as part of every release, so drift shows up while it’s still small.
The same principle carries to our other platforms. A Windows app or a browser extension popup has its own version of cold start, and the same rule applies: show something useful immediately, defer everything else.
Why it’s worth it
The discipline pays for itself. By the time launch is fast, everything else is, too — because the same rules apply everywhere.