Quick answer
App startup time is the time between a user opening your app and the app showing a screen they can use. It depends on the launch state: a cold start creates a new process, a warm start reuses some of it, and a hot start brings a running app back to the foreground. Always say which endpoint you timed, the first frame or the first usable screen.
Your monitoring dashboard puts P90 cold start at 1.9 seconds, well under the 5-second line Android vitals flags. A user on a four-year-old Android phone still waits six seconds before the feed responds to a tap. Both numbers are right. The dashboard stopped its timer at the first frame, and the user stopped theirs when the app finally did something.
What is the difference between a cold, warm and hot start?
Launch states describe how much work the system can skip. Android's app startup guide defines three, and Apple's launch-time article treats warm and cold as two ends of a spectrum.
| Launch state | What happens on Android | Closest iOS term |
|---|---|---|
| Cold start | The system creates the app process from scratch, as after a device boot or after the system killed the app | Cold launch: the process starts and its dependencies may page in from disk, as after a reboot or after a memory-heavy app evicted yours |
| Warm start | A subset of the cold-start work: the activity is recreated, either in a process that is still running or in a restarted process that gets its saved state back | Warm launch: the process has to start, but the device has not just rebooted and your app was not pushed out of memory. Apple's example is relaunching after a force quit |
| Hot start | The system brings the app's existing activity to the foreground | Resume: the process was still alive, even if suspended |
Android vitals treats a cold start of 5 seconds or longer, a warm start of 2 seconds or longer and a hot start of 1.5 seconds or longer as excessive, all timed to the first frame. Apple publishes no threshold, but its Optimizing App Launch session from WWDC 2019 sets a goal of a first frame within 400 milliseconds.
Read both definitions before you compare an Android cold-start chart with a vendor's "cold launch" segment. Apple also describes system prewarming, so a process-start timestamp you record yourself can include time when nobody was waiting.
What does iOS measure at startup?
Apple's launch-time metric runs from the tap on the icon to the first frame. Xcode Organizer and MetricKit both use it, and the launch screen is drawn inside that window. Work after the first frame does not count, even while the user waits for the feed.
Pre-main time is everything that runs before your own main() function, and no JavaScript profiler can see it. The dynamic loader dyld loads your executable and every framework it links, then static initializers such as C++ static constructors and Objective-C +load methods run. Apple's fix: embed fewer third-party frameworks and move static initializer work later.
What does Android measure at startup?
Android splits startup into two endpoints. Time to initial display (TTID) is the time to draw the first frame of the app's UI, and Android writes it to Logcat on a line containing Displayed.
Time to full display (TTFD) is the time until the app is usable, including content that loads after the first frame. You report it by calling reportFullyDrawn() on ComponentActivity; skip the call and Android reports no TTFD. Compose apps can use ReportDrawn, ReportDrawnWhen or ReportDrawnAfter instead, per the Macrobenchmark metrics reference.
Which tools measure app startup time?
Use a lab tool to find slow code and a production tool to see what users get. Listed alphabetically:
| Tool | Platform | What it measures | Best for |
|---|---|---|---|
| Android Studio CPU Profiler | Android | Which methods run during startup, down to your Application.onCreate | Finding which code runs during a cold start |
| Firebase Performance Monitoring | Android, iOS | An app start trace with Firebase's own start and stop points | One production view across both platforms |
| Macrobenchmark | Android | TTID and TTFD over repeated launches with StartupTimingMetric | Catching startup regressions in CI on a physical device |
| MetricKit | iOS | Daily launch and resume histograms from users' devices (TimeToFirstDrawMetric, ApplicationResumeTimeMetric) | Sending production launch data to your own backend |
| Sentry | React Native on Android and iOS | TTID and TTFD from its React Native SDK | Teams already using Sentry for crash reporting |
| Xcode Instruments, App Launch template | iOS | A time profile and thread-state trace for one launch | Finding a blocked main thread and pre-main cost |
| Xcode Organizer, Launch Time | iOS | Tap to first frame at the 50th and 90th percentile, per release | Comparing releases without adding an SDK |
Tool definitions differ. Firebase's app start trace on Android starts when its FirebasePerfProvider content provider finishes onCreate and stops at the first activity's onResume(). That is not the same interval as TTID, and neither matches Apple's tap-to-first-frame.
Why does startup look fast on the dashboard but slow to users?
A first frame tells the user something is happening, not that the screen works. Define "ready" per screen: a notes app may be usable once local notes load, while a ticketing app needs an authenticated ticket on screen.
Set the ready marker after data has loaded and touch handlers are attached, never on component mount; a mounted component can still be waiting for data or sitting under a modal.
Count the launches that never get there. A launch that times out before the ready marker drops out of the latency distribution, so your P90 can improve during an outage. Put failures and timeouts on their own chart.
How do you compare startup time between releases?
Measure release builds, because debug builds change the work the app does. Macrobenchmark refuses debuggable builds and discourages emulators, whose numbers do not match what users see.
Split production data by launch state, device class, OS version and entry path. A launch from a notification or deep link may wait on different data than a tap on the icon.
Report P90 with the count behind it. P90 means about 90% of measured launches finished at or below that value. It does not mean the same 10% of people always wait. In a small group, a handful of slow launches can move the tail, so read traces before you call a regression.
Percentiles do not add. The slowest native launches and the slowest first fetches often come from different sessions, so time the whole launch and take the percentile of that.
How do you reduce app startup time?
Start with the critical path, the work that has to finish before your chosen endpoint. Record one launch in Instruments or the Android Studio profiler and mark which work the first screen waits for. Work off that path will not move the number.
Defer what the first screen does not need. Apple suggests moving expensive setup out of application(_:didFinishLaunchingWithOptions:), starting storage and location services on first use, and syncing data in the background behind cached content. Check what depends on each item first.
React Native apps add JavaScript work after the native launch. Expo's metrics reference lists the usual fixes: remove unused native modules, shrink the bundle with tree shaking and Expo Atlas, and lazy-load large screens. If you use expo-updates, keep fallbackToCacheTimeout at 0, its default, so launch never blocks on an update check.
A skeleton screen can make the first frame appear sooner without making the app usable sooner. Measure both endpoints after every change and report the one that moved.
Where Expo fits
EAS Observe times startup in production React Native apps on Expo SDK 55 and later. It records cold launch, warm launch and bundle load automatically, time to first render once you wrap the root layout, and time to interactive (TTI) once you mark it. The metrics reference keeps native launch, bundle load and React rendering apart. Expo recommends a cold launch under 1.5 seconds, a warm launch under 0.5 seconds and TTI under 3 seconds including the cold launch.
You mark TTI with markInteractive() from the useObserve() hook (SDK 56 and later) or AppMetrics.markInteractive() (SDK 55). Only the first call per launch counts, so the setup guide has you call it on every entry screen, deep link targets included.
Every metric splits by app version and by EAS Update, so you can see a JavaScript update change startup without a new store release.
Limitations
EAS Observe's cold launch runs from process creation to the point the app is ready to render. That is a different interval from Apple's tap-to-first-frame and from Android's TTID. EAS Observe does not run in Expo Go, and debug builds do not send metrics by default.
No tool decides what "usable" means for your product, and a launch that never reaches the marker sends no TTI. Expo's and Apple's numbers are starting points for your own budget, not platform rules.
Next step
Take one launch trace and label its milestones with the Android startup guide or the Instruments App Launch template. Then write down which of those milestones your production dashboard reports.
Verified on 12 September 2026.
