How to Improve Mobile App Startup Time: Practical Wins for iOS & Flutter
Every 100ms of startup latency costs you users. Google's research shows a 1-second delay in mobile load time reduces conversions by 20%. Apple's own watchdog kills apps that exceed 20 seconds on launch. This guide walks through every optimization I've applied across iOS and Flutter apps to hit sub-1 second cold starts.
Quick Navigation
Measure First: Cold vs Warm Start
Before optimizing anything, understand what you're measuring. They have different root causes and different fixes.
Cold Start
App not in memory — process must be created from scratch. Includes dylib loading, static initializers,UIApplicationMain, AppDelegate.didFinishLaunching, and first screen render. This is where most pain is.
Warm Start
App in memory, backgrounded. Process already exists — only needs to foreground and restore state. Usually fast unless you have heavy applicationDidBecomeActive work.
Profiling: Find the Real Bottleneck First
Never optimize blindly. Profile first, then fix the highest-impact items.
iOS: Instruments
- Open Xcode → Product → Profile → choose Time Profiler
- Enable App Launch template to see pre-main vs post-main breakdown
- Look for: heavy static initializers, synchronous network calls, large image decoding on main thread
- Use os_signpost to add custom timing markers around your own initialization code
- Check DYLD_PRINT_STATISTICS=1 environment variable for dylib load time breakdown
Flutter: DevTools
- Run
flutter run --profile— never optimize in debug mode - Open Flutter DevTools → Performance tab → enable "Track widget builds"
- Use
Timeline.startSync/Timeline.finishSyncfor custom markers - Check the App Size tool — large assets bloat startup
- Run
flutter build apk --analyze-sizefor a full binary size breakdown
iOS-Specific Optimizations
Reduce Pre-Main Time (dylib loading)
- Merge small dynamic frameworks — each dylib adds ~2ms overhead. Aim for <6 non-system dylibs
- Use
@_implementationOnly importfor internal modules — removes them from the public interface and speeds linking - Audit your SPM/CocoaPods dependencies — unused SDKs still load
- Use static libraries where possible — they're linked at compile time, not runtime
- Enable Dead Code Stripping in build settings — removes unreachable code
Optimize AppDelegate & SceneDelegate
- Move all non-essential init out of
didFinishLaunchingWithOptions— Firebase, analytics, crash reporters can wait 1-2 seconds - Use
DispatchQueue.global().asyncfor background-safe setup tasks - Defer third-party SDK initialization until first use where the SDK supports it
- Remove synchronous network calls from startup — use cached data to render immediately
First Screen Rendering
- Show a skeleton/placeholder UI immediately — don't wait for network data to render anything
- Avoid layout on the main thread in
viewDidLoad— pre-calculate layouts in background - Use
prefetchDataSourceon collection/table views for off-screen cells - Load images asynchronously — never decode images on the main thread
- Use SwiftUI's
.taskmodifier instead ofonAppearfor async data loading — runs off main thread
Flutter-Specific Optimizations
Reduce Engine Initialization
- Use FlutterEngineGroup for multiple Flutter views — engines are expensive, share them
- Pre-warm the Flutter engine before navigation:
FlutterEngine().run()in background - Reduce
main.darttop-level code — defer plugin registration to first use - Use
DeferredComponentfor large features that aren't on the launch path
Widget Tree Optimization
- Keep the first-render widget tree shallow — defer complex subtrees with
Visibilityor lazy builders - Use
constconstructors everywhere possible — prevents unnecessary rebuilds - Replace
Column/RowwithListView.builderfor long lists — builds on demand - Cache expensive
build()results usingRepaintBoundary
Release Build Flags
- Always measure in
--releasemode — debug builds are 10x slower - Enable
--split-debug-infoto reduce binary size without losing crash symbolication - Use
--obfuscatewith split debug info for production builds - Enable tree shaking: unused icons and fonts are automatically removed in release builds
Target Numbers & How to Track Them
Track in Production
- iOS MetricKit: Built-in app launch time reporting — available since iOS 13, requires no SDK
- Firebase Performance: Automatic cold/warm start tracking with percentile breakdowns
- Custom instrumentation: Log a
app_launch_completeevent with duration from your own timing marker - Track p50, p75, p95 — median is misleading, tail latency is where users suffer
- Set a performance budget in CI: fail the build if startup time regresses >10%
Quick Win Priority List
- Profile first — identify your actual bottleneck (pre-main vs post-main vs render)
- Defer SDK init — Firebase, analytics, crash reporters: move out of didFinishLaunching
- Show cached data immediately — never block first render on a network call
- Reduce dylib count — merge small frameworks, use static libs
- Compress launch assets — launch screen images load synchronously
- Add MetricKit — track production p95 before and after each change
Related Services & Further Reading
Struggling with Slow App Startup?
I offer performance audits for iOS and Flutter apps — profiling, identifying root causes, and implementing fixes. Most apps see 40-60% startup improvement after a focused audit.