Quick answer
To measure mobile app performance, collect five metrics from real user sessions, at the 90th percentile, per release: cold start time, time to interactive, per-screen render time, slow and frozen frames, and crash-free sessions. Google flags a cold start of 5 seconds or more as slow. Apple's target for the first frame is 400 milliseconds. Numbers from your own device in a debugger do not count.
Why performance complaints are hard to act on
A user writes a one-star review that says "app is slow". Your team opens the profiler, taps through the app on a new iPhone on office Wi-Fi, and everything is fine. The review stays up. Two weeks later the store listing shows a dip in conversion and nobody can say which release caused it.
Performance measurement at most mobile teams looks like that: anecdotes from users, profiler runs from developers, and no number that connects the two. The fix is not more profiling. You need a small set of production metrics, collected from every device your app runs on, compared release over release.
By the end of this guide you will know which five metrics to collect, what "good" looks like for each, and the three ways to instrument them (platform tools, a third-party SDK, or a React Native SDK).
Why the profiler lies to you
Development tools answer "how fast is this code on my machine". Users experience "how fast is this app on a four-year-old Android phone, on cellular, with Battery Saver on". Those are different questions, and two habits cause most of the gap.
Measuring on your own device. A flagship phone plugged into a laptop hides everything that makes production slow: thermal throttling, low-power mode, slow storage, a metered connection. Google's Android vitals data comes from real devices for exactly this reason.
Reporting averages. An average hides the slow tail. If 10% of your users wait eight seconds to see content and the other 90% wait one second, the average is 1.7 seconds and looks healthy. Report the 90th percentile (P90) and the 99th. The P90 is the experience one in ten users has every time they open the app.
Which five metrics show mobile app performance?
1. Cold start time
What it measures. Time from process creation until the app has loaded its code and resources and is ready to draw its first frame. A cold start happens after install, after an update, after a reboot, or after the OS killed the app to reclaim memory.
Why it matters. Cold start is the first thing every new user experiences, and the store install-to-first-open funnel is where most apps lose people.
Thresholds. Android vitals considers a cold start of 5 seconds or longer slow, and a warm start of 2 seconds or longer slow. Apple's guidance since WWDC 2019 is to render the first frame within 400 milliseconds. Expo's own recommendation for React Native apps is a cold launch under 1.5 seconds.
How to measure it. On iOS, MetricKit reports launch histograms per day, and Xcode Organizer shows them per version. On Android, the Play Console shows cold, warm and hot start distributions under Android vitals. Both are free and both are per-platform, so you will read two dashboards.
2. Time to interactive (TTI)
What it measures. Time from launch until the user can tap, scroll and get a response. The clock keeps running past the first frame and past the moment the splash screen goes away; it stops when the app is usable.
Why it matters. TTI is the metric users mean when they say "slow". An app can draw a skeleton screen in 300 milliseconds and still make the user wait four seconds for the feed to become tappable. TTI catches that; first-frame metrics do not.
Thresholds. There is no platform-enforced threshold, because the platform cannot know when your app is "ready". Expo recommends under 3 seconds including the cold launch. Android's closest built-in equivalent is Time to Full Display (TTFD), which you report yourself by calling reportFullyDrawn().
How to measure it. You have to mark the moment yourself, in code, after data has loaded and touch handlers are attached. Every SDK that reports TTI (Firebase Performance, Sentry, EAS Observe) needs this one call from you. Get it wrong (mark on mount, before data arrives) and your TTI will look better than reality.
3. Per-screen render time
What it measures. Time from a navigation action until the destination screen is drawn, and separately, until it is interactive.
Why it matters. Startup is one screen. Users spend their session on the other twenty. A checkout screen that takes two seconds to render costs more revenue than a slow launch, and startup metrics will never show it. Per-screen render time answers "which screens are slow", and most teams do not have it.
Thresholds. None from Apple or Google. Set your own budget per screen. A common starting point is 1 second to first render on a mid-range device at P90.
How to measure it. Instrument the navigation layer. On native iOS and Android that means timing your own view controller or activity transitions. In React Native, the router (Expo Router or React Navigation) knows when a navigation starts and when the screen gains focus, so an integration at that layer can time every route with no per-screen code.
4. Slow and frozen frames
What it measures. How often the UI misses its drawing deadline. A slow frame takes longer than 16 milliseconds (the 60 Hz budget). A frozen frame takes longer than 700 milliseconds, which the user sees as a hang.
Why it matters. Frame health is the "smoothness" number. An app can have a fine TTI and still stutter through every scroll.
Thresholds. Android vitals flags an app for slow rendering when more than 50% of frames exceed 16 milliseconds, or more than 0.1% of frames exceed 700 milliseconds. Frozen frames during startup deserve special attention. Even one is a sign of synchronous I/O or large JSON parsing on the main thread.
How to measure it. Play Console reports both for Android. On iOS, MetricKit reports hang rate (time the main thread was blocked for over 250 milliseconds). Third-party SDKs report frame health per session and, if you are lucky, per screen.
5. Crash-free sessions and ANR rate
What it measures. The share of sessions that ended without a crash, and on Android, the share of users who hit an Application Not Responding dialog.
Why it matters. Stability is performance to the user. Google also uses it against you: apps above the bad-behavior thresholds lose visibility on Google Play.
Thresholds. Google's Play Console thresholds are a user-perceived crash rate of 1.09% of daily users and a user-perceived ANR rate of 0.47% of daily users. Apple publishes no threshold, but Xcode Organizer shows crash counts per version.
How to measure it. Play Console and Xcode Organizer give you the platform view for free. A dedicated crash reporter (Sentry, Firebase Crashlytics, BugSnag) gives you symbolicated stack traces, grouping and alerting. Crash reporting is the one area where a dedicated tool is not optional for a team of any size.
How to compare performance across releases
A dashboard that shows P90 TTI going up on Tuesday tells you nothing. The same number split by app version tells you which build did it.
Every metric above should be filterable by three dimensions: app version, OS version, and device class. For React Native apps add a fourth: the JavaScript bundle version. Teams that ship over-the-air JavaScript updates can change what users run several times a week without a store release, and a regression in one of those updates is invisible to a dashboard that only knows about store versions.
Set a budget per metric, check it per release, and treat a P90 regression as a release blocker the same way you treat a failing test.
Which metrics to leave out
Development-mode FPS counters. They measure your machine.
Memory as a headline metric. Memory matters when it causes a termination. Watch low-memory warnings and out-of-memory terminations instead of average footprint.
Network latency on its own. A slow API shows up in TTI and per-screen render time, where you can see whether it mattered. Track the network breakdown as a cause, not a headline.
Synthetic scores. There is no Lighthouse for mobile apps. A single composite number hides which metric moved.
How to instrument the five metrics
Platform tools, free. Android vitals in Play Console and MetricKit plus Xcode Organizer on iOS. Cover cold start, frame health and crashes with no code. Gaps: no TTI unless you report it yourself, no per-screen metrics, two separate dashboards with different definitions, and no view of over-the-air JavaScript updates.
A third-party observability SDK. Sentry, Datadog RUM and Firebase Performance Monitoring each cover most of the five. Sentry and Firebase Crashlytics are the strongest choices for crash reporting. Datadog fits teams already running it for backend traces. Costs scale with sessions, so read the pricing page before you turn on 100% sampling.
A React Native performance SDK. If your app is built with React Native, the JavaScript layer has its own performance story (bundle load time, route render time, update download time) that platform tools cannot see.
Where Expo fits
EAS Observe is Expo's production performance monitoring service for React Native apps. It collects cold and warm launch time, bundle load time, time to first render and TTI automatically, and with the Expo Router integration it times first render and TTI for every route. Every metric can be compared across app versions and across EAS Update releases, which is the gap platform tools leave for teams shipping JavaScript updates.
Setup is two steps. Install the library and wrap your root layout:
Then mark the moment your first screen is usable, so TTI reflects reality:
Each TTI event carries slow and frozen frame counts, thermal state, low-power mode and a summary of network requests made during launch, so you can tell a slow launch caused by the network from one caused by the app.
Limitations. EAS Observe requires Expo SDK 55 or later, an EAS project and a development or production build (it does not run in Expo Go). Error reporting is in preview: on SDK 57 and later it records JavaScript errors, and from expo-observe 57.0.21 it also records native crashes, but native stack traces are not symbolicated in the dashboard. ANR events and out-of-memory terminations are not recorded. For symbolicated native crash reports and ANRs, pair it with a dedicated crash reporter. Metrics are free up to 100,000 events per month and usage-based after that.
Thresholds at a glance
| Metric | Threshold | Source |
|---|---|---|
| Cold start | 5 s or more is slow (Android). 400 ms to first frame target (Apple). Under 1.5 s recommended (Expo) | Android vitals, WWDC 2019, Expo docs |
| Warm start | 2 s or more is slow (Android). Under 0.5 s recommended (Expo) | Android vitals, Expo docs |
| Time to interactive | No platform threshold. Under 3 s including launch (Expo) | Expo docs |
| Slow frames | More than 50% of frames over 16 ms | Android vitals |
| Frozen frames | More than 0.1% of frames over 700 ms | Android vitals |
| User-perceived crash rate | 1.09% of daily users | Play Console |
| User-perceived ANR rate | 0.47% of daily users | Play Console |
Next step
Pick one metric, TTI, and get a P90 number per release this week. If your app is React Native, the EAS Observe getting started guide takes about fifteen minutes. If it is native, start with Android vitals and reportFullyDrawn().
