Optimizing Mobile App Cold Start Time
Cold start is launching an app from zero: process doesn't exist in memory, OS creates process, loads binary, initializes runtime, starts Application/AppDelegate, renders first screen. On Android this is from icon tap to Activity.onResume(). On iOS — from tap to first rendered frame.
Cold start slowdown almost never has one cause. It's accumulated debt: SDK initializations in Application.onCreate(), heavy operations on main thread, bloated splash screen, synchronous database reads at startup.
Where Time Gets Lost: Android
Android Vitals in Play Console shows "Startup time" metric — percentage of sessions with cold start > 5 seconds. But this is an aggregate. For diagnosis you need Android Studio Profiler → App Startup or Perfetto.
Typical picture during audit: Application.onCreate() takes 800-1200 ms on mid-range device, and most of it is synchronous Firebase, Amplitude, AppsFlyer, OneSignal, and three more SDKs initialization. Each does SharedPreferences.read, creates HandlerThread, registers BroadcastReceiver.
Solution: App Startup Library (androidx.startup) with explicit dependency graph. SDKs needed immediately (Crashlytics) — sync. Analytics, push — via ContentProvider lazy init or background thread with 2-3 second delay after first render.
Second loss source — Dagger/Hilt dependency graph at startup. If @Singleton components are heavy (Room database, Retrofit instances) and created all at once — visible as drop in Profiler right after onCreate. Solution: @Lazy<T> for components not needed on first screen, backgroundScope.launch for repository init.
Baseline Profiles (Jetpack) — pre-compile hot code paths to AOT before JIT sees them. ProfileInstaller + BaselineProfileRule in tests allows reducing cold start by 30-40% on first launches after install/update. This isn't magic — explicit markup "these classes need early compilation".
Where Time Gets Lost: iOS
os_signpost + Instruments Time Profiler — only right way to see real picture. Xcode shows time from tap to applicationDidFinishLaunching, separately — time to first significant render.
Main culprits on iOS: +load methods in Objective-C classes and C++ static constructors. They execute before main(), and their time isn't visible in usual Profiler without special markup. DYLD_PRINT_STATISTICS in environment variables shows real pre-main time.
Swift initialization is faster than Obj-C, but traps exist: @UIApplicationMain class with heavy init, singletons via static let shared = ... created in application(_:didFinishLaunchingWithOptions:) chain.
URLSession, CoreData stack, Keychain — all should initialize lazily or in background. CoreData NSPersistentContainer.loadPersistentStores — async by default, but developers often wrap it in semaphore, making it synchronous call on main thread.
Metrics and Target Values
| Device Type | Good | Acceptable | Poor |
|---|---|---|---|
| Android high-end | < 1.0 s | 1.0–2.0 s | > 2.0 s |
| Android mid-range | < 2.0 s | 2.0–4.0 s | > 4.0 s |
| iPhone (last 3 generations) | < 0.8 s | 0.8–1.5 s | > 1.5 s |
| iPhone (5+ years) | < 1.5 s | 1.5–3.0 s | > 3.0 s |
Measure metrics on real devices, not emulator. Emulator doesn't reflect real I/O and JIT performance.
Flutter and React Native
In Flutter cold start bottlenecks on Dart VM and engine initialization. FlutterActivity vs FlutterFragmentActivity — 50-100 ms difference. Pre-initializing engine via FlutterEngineCache + FlutterEngineGroup allows engine reuse between launches. Splash screen via flutter_native_splash properly synced with native launch screen.
In React Native the problem is JS bundle load time. Hermes engine (compile to bytecode) reduces parse-time 2-3x vs JSC. RAM Bundles and inline requires allow loading only code needed for first screen.
Optimization Process
First measure — without baseline metrics unclear what to optimize. Profiler session on 3-5 real device types. Next — analyze hot paths, prioritize by contribution to total time. Implement changes iteratively with measurement after each change. Final before/after comparison on same device set.
Work timeline: one to three weeks depending on architecture complexity and platform count.







