Mobile performance
Users feel performance before they notice features. An app that stutters while scrolling or takes four seconds to open feels broken, even when it’s correct.
The main thread and the frame budget
Both iOS and Android draw the UI on a single main thread (also called the UI thread). At 60 frames per second, each frame has about 16.7 ms for handling input, running layout and drawing; at 120 Hz it’s about 8.3 ms. If work on the main thread takes longer, a frame is dropped and the user sees jank.
Anything slow must leave the main thread:
- network calls and database queries;
- JSON parsing of large responses;
- image decoding and resizing;
- file reads and writes.
Use the platform’s tools, such as Kotlin coroutines with Dispatchers.IO on Android, and Swift concurrency with async/await and actors on iOS. Hop back to the main thread only to update the UI.
Android shows an “Application Not Responding” dialog when the main thread is blocked for about 5 seconds. iOS’s watchdog terminates apps that block too long at launch.
Lists that scroll smoothly
Long lists must recycle their row views: RecyclerView or LazyColumn on Android, UICollectionView, UITableView or SwiftUI’s List and LazyVStack on iOS. Only the visible rows exist; the rest are reused as you scroll.
To keep scrolling smooth:
- keep row layouts shallow and avoid expensive measurement;
- give items stable ids, so updates animate instead of rebuilding the list;
- load images asynchronously and cancel loads for rows that scroll away;
- paginate data instead of loading ten thousand rows up front.
Images
Images are the usual cause of both memory pressure and slow screens. A 4000×3000 photo decodes to about 48 MB in memory (4 bytes per pixel), even if it’s shown as a 100-point thumbnail. Ask the server for the size you need, or downsample when decoding. Use an image library such as Coil, Glide, Kingfisher or Nuke: they cache on disk and in memory and handle cancellation.
App start-up
A cold start launches the process from scratch, and it’s the slowest and most noticed case. Keep it fast by doing less:
- defer initialisation of SDKs and features the first screen doesn’t need;
- avoid synchronous network or disk work before the first frame;
- show real content, or a skeleton of it, as soon as possible, rather than a long splash screen.
Measure with Android Studio’s profilers and Macrobenchmark, and Xcode Instruments’ App Launch template. Watch real-user metrics too: devices in your users’ hands are often far slower than the one on your desk.
Battery and data
Every network request wakes the radio, which then stays in a high-power state for several seconds. Batch requests instead of trickling them, avoid polling (use push notifications), and let background work wait for Wi-Fi or charging when it can. Location and sensor access drain batteries quickly; request the lowest accuracy that works and stop updates when they’re not needed.
Measure, then optimise
Profile on a low-end device in release mode: debug builds are much slower and give misleading results. Find the biggest cost, fix it, and measure again.