Lag in a mobile app is a noticeable delay between user action and interface response, caused by main thread overload, memory leaks, or suboptimal I/O operations. Unlike glitches related to logical errors, lag is a performance problem: the app works correctly but slowly. According to AppDynamics Mobile App Performance Report 2024, 62% of users delete an app if it lags for more than 3 seconds. Diagnosing lag requires profiling CPU, memory, and network using Android Studio Profiler and Xcode Instruments.
Key Takeaways
Lag in a mobile app is a subjectively noticeable delay between user action (touch, swipe, text input) and interface response. Technically, lag is measured as the time between input event and full frame render: the comfortable threshold is up to 100 ms, noticeable from 200 ms, critical over 500 ms.
In user terminology, “lags” and “slows down” are often used interchangeably, but technically lag is a fixed delay (e.g., 300 ms on every tap), while “slows down” is an intermittent slowdown: the app works smoothly then freezes for a second. A glitch, unlike lag, is related not to speed but to display correctness.
Google Play and App Store consider performance metrics when ranking apps. ANR rate, jank frequency, and startup time affect search visibility and install conversion. An app with persistent lag loses up to 40% of users after the first launch.
Lag occurs when the main UI thread fails to process frames at 60 FPS (16.6 ms per frame) or 120 FPS (8.3 ms). Let's look at the main sources of delays.
Any synchronous operation in the UI thread — reading from SharedPreferences, working with a database via Room without suspend, decoding an image into Bitmap — blocks frame rendering. On Android this causes jank, on iOS it causes Core Animation render delay.
When the Garbage Collector on Android or ARC on iOS performs memory cleanup, all threads are paused. Frequent GC pauses occur when creating many temporary objects — for example, creating a new ViewHolder instance on every adapter call. This manifests as janky scrolling.
Nested ConstraintLayout, multiple LinearLayouts, overlapping Views — each nesting level increases measure and layout pass time. Xcode indicates that a deep layer hierarchy (over 10 levels) causes a 20-30% FPS drop.
Profilers built into IDEs and system monitoring tools are used to identify lag causes. Each tool solves its own task.
CPU Profiler shows which methods consume CPU time and in which threads they execute. If a heavy computation method runs in the main thread — that is the root cause. Recording a trace with sample Java Method enabled allows you to see the call stack at any moment and find hot spots.
The equivalent tool for iOS — Time Profiler — collects stack samples every millisecond and shows what percentage of CPU time each method consumes. Combined with the Main Thread Only flag, it filters only main thread operations, directly pointing to lag sources.
Slow network requests create the impression of lag even if the UI thread is not blocked. Network Profiler in Android Studio and Network Link Conditioner in Xcode allow simulating slow connections and identifying how the app behaves in real-world conditions. Chunked responses without progress and large JSON payloads are typical sources of apparent lag.
Example of profiling a network request with OkHttp with timing:
class TimingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = System.nanoTime()
val response = chain.proceed(chain.request())
val duration = (System.nanoTime() - start) / 1_000_000
Log.d("Timing", "Request took $duration ms")
return response
}
}
Fixing lag requires systematic work: from optimizing a single method to architectural changes. Let's look at the most effective techniques.
Kotlin Coroutines with Dispatchers.IO for network requests and Dispatchers.Default for computations ensure the main thread stays free for UI. On iOS Grand Central Dispatch with queue .global(qos: .userInitiated) for background tasks and .main for UI updates is the standard approach. Avoid sync operations between queues.
RecyclerView on Android and UICollectionView on iOS require proper configuration: ViewHolder with minimal object creation in onBindViewHolder, DiffUtil for change calculation, prefetching for data loading in advance. On iOS use diffable data source for animated updates without manual management.
Loading the same image on every scroll is guaranteed lag. Coil (Android) and Kingfisher (iOS) cache images in memory and on disk, ensuring instant display on repeat requests. For data, use Room with a caching layer based on Flow or Combine.
Example of configuring image caching with Coil on Android:
val imageLoader = ImageLoader(context) {
memoryCachePolicy(CachePolicy.ENABLED)
diskCachePolicy(CachePolicy.ENABLED)
crossfade(true)
size(512, 512)
}
// Loading with auto-caching enabled
imageView.load("https://example.com/image.jpg") {
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
Preventing lag is cheaper than fixing it in production. Preventive measures are built into the development process at the tool and architecture level.
StrictMode is a built-in Android tool that detects accidental I/O operations and network calls on the main thread during development. Enable it in Application.onCreate with penaltyDeath policy for critical violations. This is the only way to ensure the developer sees the issue before committing.
The iOS equivalent — Main Thread Checker in Xcode, part of Runtime Sanitization — automatically checks that all UIKit and AppKit calls execute on the main thread. Enable it in the Debug build scheme and aim for zero warnings in CI.
Add Macrobenchmark (Android) and XCTMetrics (iOS) runs to your CI pipeline to measure startup time, scroll FPS, and memory usage. Set thresholds: if a new commit increases startup time by more than 5% — the build fails.
Frequently Asked Questions
Lag is a subjective feeling of delay that can occur even at high FPS if the delay is caused by input processing time rather than rendering. Low FPS (below 30 fps) is one cause of lag, but not the only one.
Use Frame Timing API on Android (Choreographer) and CADisplayLink on iOS to measure time between frames. Google Play Vitals shows jank rate in real-world conditions. For precise measurements use Macrobenchmark with scroll scenarios.
Older devices have fewer CPU cores, less RAM, and slower memory. An operation that takes 5 ms on a flagship may take 50 ms on a budget device. Test performance on lower-end devices and set up Baseline Profiles for AOT compilation.
Yes, this is one of the most effective methods. High-resolution images consume a lot of memory and CPU time for decoding. Use downscale to View size, WebP (Android) and HEIC (iOS) formats, and caching via Coil or Kingfisher.
SwiftUI automatically optimizes updates through diffing, reducing the risk of lag when data changes. However, complex hierarchies and frequent body rebuilds can cause FPS drops. UIKit gives more control over performance but requires manual optimization.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also