App performance optimization
App performance optimization company that proves every fix with numbers
We are an app performance optimization company that takes a slow, janky or crash-prone iOS or Android app, profiles it on real devices, fixes the biggest causes first and shows you before and after numbers for each change. Optimization is part of our mobile app maintenance services; this page explains the profiling tools we use, the fixes that usually pay off and how we report results so you can see what your budget bought.
- Profiled on mid-range and older devices
- Fixes ranked by measured user impact
- Before and after report per change
- Budgets added to CI to hold the gains
What an app performance optimization company should change
Performance work goes wrong in two ways. Either a team rewrites large parts of the app on a hunch and nothing measurable improves, or it micro-tunes code that was never the bottleneck. Both burn budget. The fix is discipline: measure, change one thing, measure again on the same device.
We start from field data, because it shows what real users feel: launch time, hang rate and memory in Xcode Organizer, crash and ANR rates in Android vitals, and whatever your performance SDK records. Then we profile in the lab to find causes, on the mid-range and older phones your analytics show, not just on flagships. If you need a full measurement baseline first, that is our app performance testing service; optimization can start from its report or from our own shorter audit.
The goal is not a perfect score. It is to move the metrics that cost you users, reviews or store visibility, by an amount you can see, and to stop them sliding back.
Profiling tools we use
Every finding comes from a trace, not an opinion. These are the main tools, matched to the problem.
| Problem | iOS | Android | Cross-platform |
|---|---|---|---|
| Slow launch | Instruments App Launch and Time Profiler; os_signpost intervals | Macrobenchmark startup tests; Perfetto system traces | Flutter DevTools timeline; React Native profiler and Hermes sampling |
| Jank and hitches | Instruments Animation Hitches; SwiftUI view body instrument | Perfetto FrameTimeline; JankStats; Compose recomposition counts in Layout Inspector | Flutter performance overlay; React Native Perf Monitor |
| Memory growth and leaks | Allocations, Leaks, memory graph debugger | Memory Profiler heap dumps; LeakCanary | Flutter DevTools memory view |
| Main-thread blocking and ANRs | Time Profiler; MetricKit hang diagnostics | StrictMode; ANR traces from Play Console | Platform tools on the native thread |
| Network | Instruments network template; Network Link Conditioner | Network Inspector; emulator throttling | Proxy tools such as Charles or Proxyman |
| App size | Xcode app thinning size report | APK Analyzer; R8 shrinking reports | Flutter –analyze-size; bundle analysers |
Profiling happens on release builds on physical devices. Debug builds and simulators exaggerate some costs and hide others.
Common fixes that pay off
Most slow apps share the same few causes. These fixes come up on almost every engagement, roughly in order of how often they help.
Defer start-up work
Analytics, ads, chat and crash SDKs all initialising on the main thread before the first frame. We move non-critical setup off the main thread or after first render, and lazy-load features the first screen does not need.
Baseline Profiles on Android
A Baseline Profile tells Android which code to compile ahead of time, cutting interpretation during launch and first scrolls. Generated with Macrobenchmark and shipped in the app bundle, it is one of the cheapest startup wins available.
Right-sized images
Full-size photos downloaded and decoded for thumbnails cause memory spikes and dropped frames. We request sized images from the server or CDN, decode off the main thread and cache with Coil, Glide, Kingfisher or SDWebImage.
Smarter lists
Lists that rebuild every row on each change. Stable keys in Compose and SwiftUI, diffing in RecyclerView, avoiding heavy work in view bodies and cell binding, and pagination instead of loading everything.
Main thread kept for UI
Database queries, file I/O and JSON parsing moved to background threads with coroutines or Swift concurrency. This is the usual fix for ANRs on Android and hangs on iOS.
Fewer, cheaper network calls
Screens that need ten requests to render. We combine calls, add caching and conditional requests, compress payloads and show cached content first, often with changes on both the app and the API.
Leaks closed
Retained view controllers, fragments, closures and listeners that grow memory every time a screen opens. Fixed leaks reduce background terminations, which users experience as the app restarting.
Smaller app size
Unused resources, duplicated libraries and uncompressed assets removed; R8 shrinking enabled on Android and app thinning checked on iOS. Smaller apps download and update faster on slow connections.
Users calling your app slow?
Share your store links and read access to Play Console or App Store Connect. Within one business day we reply with what the field data shows and where we would start.
Before and after: how we report results
Every change is measured twice on the same build configuration, devices and network profile: once before, once after. We report the median and the slow tail, because a fix that helps the average but leaves the slowest users stuck is only half a fix.
The report has one row per change with the metric, the device, the before and after values and the trace that explains it. We do not publish client numbers without permission, so the table below shows what each row measures and the target it is held to. You see which changes delivered and which did not. Mixed outcomes are common: a change that cut cold start on a low-end Android noticeably but did nothing on a recent iPhone, or a caching change that improved repeat visits but not first launch. Both are useful to know.
Lab numbers are then checked against field data after release. On Android that means Android vitals and your performance SDK. On iOS, Xcode Organizer shows launch time, memory, hang rate, battery and disk writes from real users, and MetricKit sends per-device reports to your own backend. The release is judged on what users experience, not on our lab.
What we measure, on which devices, and the target
Every report row is built from this table. Where Google or Apple publish a threshold, that is the floor we work to. Where they do not, the target is set from your own baseline in the audit, so you are never judged against someone else’s app.
| Metric | Measured on | Tools | Target |
|---|---|---|---|
| Cold start, time to initial display (Android) | The low-end and mid-range Android models with the most users in your Play Console device data | Macrobenchmark startup tests; Android vitals | Well under 5 seconds, the point at which Android vitals flags a cold start as excessive; the exact target comes from the audit |
| Warm and hot start (Android) | The same reference Android devices | Macrobenchmark; Android vitals | Under 2 seconds warm and 1.5 seconds hot, Android vitals’ excessive-start thresholds |
| Time to full display (Android) | The same reference Android devices | reportFullyDrawn() with Macrobenchmark | Set per app from the baseline, because “fully usable” means something different on each first screen |
| Slow and frozen frames on key scrolling screens (Android, View-based UI) | A mid-range Android on your busiest lists and feeds | JankStats; Perfetto FrameTimeline; Android vitals | No frozen frames (over 700ms); fewer slow frames (over 16ms) than the baseline |
| Hang rate (iOS) | The oldest iPhone models in your Xcode Organizer device list | Xcode Organizer; MetricKit; Time Profiler | Lower median and 90th-percentile hang rate than the previous version; Organizer counts any freeze over 250 ms |
| Hitch rate (iOS) | An older iPhone on scrolling and transition-heavy screens | Xcode Organizer; Instruments Animation Hitches | Lower than the previous version on each screen we change |
| User-perceived crash and ANR rate (Android) | All Android users, and each phone model separately | Android vitals, 28-day average | Below 1.09% crashes and 0.47% ANRs overall, and below 8% on any single phone model |
| Memory after repeated navigation | An older iPhone and a low-RAM Android | Allocations and Leaks in Instruments; LeakCanary | No growth after 20 open-and-close cycles of the same screen |
Thresholds are from Google’s Android vitals startup documentation, its slow-rendering and crash pages, and Apple’s Xcode Organizer documentation. Reference devices come from your own store data, not from whatever phones are newest in our office.
Why Android vitals make this urgent
On Google Play, poor stability has a direct cost beyond reviews. Google’s bad-behaviour thresholds are a user-perceived crash rate of 1.09% and an ANR rate of 0.47% overall, with 8% for any single phone model, measured as a 28-day average. Going over a threshold can reduce your app’s visibility on Google Play and add a warning to your store listing, according to Google’s Android vitals documentation.
Because the window is 28 days, a fix takes time to show up in the number. That is another reason to ship optimizations in staged rollouts and track crash-free users and ANR rate by version, rather than waiting for the Play Console average to move.
On iOS there is no published penalty threshold, but Organizer compares your app with goals based on similar apps and can alert you to regressions of 75% or more against the previous four versions. We treat those alerts as tickets.
Quick wins we check first
- SDK initialisation order at launch
- Baseline Profile present and current
- Image sizes versus display sizes
- Main-thread disk and database work
- Number of requests per screen
- R8 shrinking and resource cleanup
How an optimization engagement runs
Short, measured cycles. You see results after each one, not only at the end.
Audit
Read field data, profile the critical flows on reference devices, and produce a ranked list of causes with estimated impact and effort.
Agree targets
Pick the metrics that matter for your app, such as cold start on a low-end Android or ANR rate, and set a target for each.
Fix in sprints
Two-week sprints, each with a set of changes, before and after measurements and a test build on TestFlight and Play internal testing.
Release in stages
Staged rollout on Google Play and phased release on the App Store, watching crash-free users and field metrics by version.
Hold the gains
Add performance budgets and benchmark tests to CI through our automated app testing setup, so the next feature cannot quietly undo the work.
When optimization is not enough
Sometimes profiling shows the problem is structural: an abandoned framework, a WebView wrapper doing work a native screen should do, or an architecture where every screen reloads all data. Tuning will help a little, but the right answer is a partial rebuild. We will tell you that plainly and point you to our app modernization services, with the profiling evidence behind the recommendation.
Backend latency is the other common limit. If the API takes two seconds, no client-side change makes the screen fast. Our mobile app backend development team can work on both sides together. Crashes that are not about speed belong with our app bug fixing services.
App performance optimization: FAQs
How do you decide what to optimize first?
We rank causes by measured user impact and by effort. Field data shows which metrics and devices are hurting real users, and profiling on those devices shows which code is responsible. A fix that affects every launch on popular low-end phones beats one that shaves time off a rarely used screen, even if the second is more interesting technically.
How long does mobile app performance optimization take?
The audit is short and gives you a ranked list with effort estimates. Fixes then run in two-week sprints, with before and after numbers each time, so you can stop when the gains level off. Field metrics such as the Android vitals 28-day average take a few weeks after release to fully reflect a fix.
Can you optimize a Flutter or React Native app?
Yes. Flutter DevTools and the React Native profiler show framework-level costs such as rebuilds and slow JavaScript work, and we pair them with native profilers for startup, memory and the platform thread. Common fixes include reducing rebuilds, moving heavy work off the UI thread, image sizing and trimming start-up plugins.
What is a Baseline Profile?
A Baseline Profile is a list of classes and methods an Android app ships so the system can compile them ahead of time. That reduces interpreted code during launch and early interactions, so startup and first scrolls are faster. It is generated with Macrobenchmark and should be regenerated as the app changes, ideally in CI.
Will you guarantee a specific improvement?
No, and be wary of anyone who does before profiling your app. We commit to fixing the highest-impact causes first and reporting before and after numbers for every change on the same devices. After the audit we can give expected ranges for the main fixes, based on what the traces show.
How do we stop the app getting slow again?
Set performance budgets for key flows on reference devices and enforce them in CI with benchmark tests, so a change that breaks a budget fails the build. Track field metrics by app version after each release. Both fit into a maintenance plan, with a monthly health report showing performance trends.
Make your app measurably faster
Book a 30-minute call. We will look at your field data with you and agree the two or three metrics worth moving first.
