The cost of a cold start
A cold start on BoardSnap used to take 3.2 seconds. That's 3 seconds where someone standing in front of a whiteboard is watching a loading indicator. Here's what we did about it.
A whiteboard photo app has a specific cold-start problem. Your user is standing in front of a whiteboard at the end of a meeting. They pull out their phone. They open BoardSnap. They wait.
Every second of that wait is a second where someone might change their mind about whether snapping the board is worth the effort. Meeting-room patience is not infinite patience.
BoardSnap's first build had a cold start (fresh launch on a clear cache) of approximately 3.2 seconds to the camera viewfinder on an iPhone 12. That's too slow.
### Where the 3.2 seconds came from
I profiled the startup and found the time split roughly like this:
- Main bundle initialization: 0.4s (unavoidable)
- Core Data stack setup: 0.9s (expensive — had to initialize the persistent container before showing anything)
- Project list fetch: 0.3s (database query on startup)
- Camera session setup: 0.8s (AVCaptureSession initialization and authorization check)
- VisionKit setup: 0.4s (document camera controller initialization)
- UI render: 0.4s (first frame)
Total: 3.2s. The two biggest opportunities were Core Data and camera session setup.
### Fix 1: Async Core Data initialization
Core Data was blocking the main thread during startup. The app waited for the persistent container to be ready before drawing any UI.
Fix: initialize Core Data asynchronously from a background thread during the splash/launch phase. The UI loads and shows the project list skeleton immediately; the actual project data populates as soon as Core Data is ready (typically 200–300ms later on subsequent launches).
This moved Core Data initialization off the critical path. The user sees the app faster; the data arrives a fraction of a second later. In practice, on most devices, the Core Data initialization completes before the user has time to interact with the list.
Time saved: ~0.7s on cold start (Core Data still takes 0.9s; it just no longer blocks the UI)
### Fix 2: Background camera session setup
Camera session setup was also blocking. The app was waiting for AVCaptureSession to be ready and camera authorization to be confirmed before showing the camera view.
Fix: start the camera session setup on a background thread when the app launches, not when the user taps the camera icon. By the time they navigate to the camera, the session is already warm.
For subsequent opens (warm start), the camera session stays alive in the background, so tapping the camera icon immediately shows the live viewfinder with no setup delay.
Time saved: ~0.6s on cold start, nearly all of warm-start camera latency eliminated
### Fix 3: Pre-warming VisionKit
Similar approach: initialize VNDocumentCameraViewController in the background on app launch. The controller is ready before the user navigates to it.
Time saved: ~0.3s
### Results
Current cold-start time to camera viewfinder: 1.4 seconds on iPhone 12, 0.9 seconds on iPhone 15 Pro.
Warm start (reopening after backgrounding): under 0.3 seconds to live camera.
The 1.4-second cold start isn't zero, but it's below the threshold where users perceive loading as slow. The 0.3-second warm start is effectively instant.
### What remains
The 0.4s main bundle initialization is largely fixed — it's the OS overhead of launching any app of our size. Getting below 0.3s cold start would require reducing the app bundle size (which affects download size and potentially feature scope) or moving to a thinner native layer.
The 0.4s UI render on first frame is normal for a complex SwiftUI app with animations. This could be reduced by shipping a simpler first frame that progressively enhances, but the visual cost isn't worth the latency savings at this point.
1.4 seconds is acceptable. Sub-1-second would be better. That's on the roadmap.
Snap your first board today.
See the workflow this post talks about — free on the App Store.