FPS (Frames Per Second) is a metric that shows how many individual frames a graphics system renders in one second. In mobile development, FPS is a standard UI performance indicator: the higher the FPS, the smoother the animations and the more responsive the interface. According to Google Android Performance, 2025, the target FPS for mobile applications is 60 frames per second — the threshold at which the human eye perceives motion as continuous and smooth.
Key Takeaways
FPS (Frames Per Second) is a unit of measurement for frame rate used in computer graphics, video, and mobile interfaces. Each frame is a static image displayed on the screen for a short period of time. With rapid frame changes, the brain perceives them as continuous motion — this effect is called persistence of vision. For mobile applications, FPS is a critical metric because any dropped frame turns a smooth animation into a noticeable stutter. The application must render each frame strictly within the time budget: 16.6 ms for 60 FPS, 11.1 ms for 90 FPS, 8.3 ms for 120 FPS.
FPS is measured not only for UI but also for games, video, and camera. In games, FPS depends on scene complexity, texture quality, and GPU power. In video, FPS is fixed (24, 30, 60 fps) and determined by the content. In mobile applications, FPS depends on UI code efficiency: Layout complexity, number of Views, redraw frequency, and GC (Garbage Collection) work. According to Apple WWDC 2022, average FPS in an application can drop by 10–15% due to inefficient collection updates (reloadData instead of insert/delete/dequeueReusableCell). Measuring FPS in real time is standard practice for QA engineers and developers working on performance.
Calculating FPS in a mobile application is based on measuring the time between consecutive frames. The simplest formula: FPS = 1000 / deltaTimeMs, where deltaTimeMs is the interval between the completion of the previous frame and the completion of the current one. If the current frame was rendered in 20 ms, FPS = 1000 / 20 = 50. However, in practice, FPS is rarely stable even within a single second: a typical profile includes frames of 12–16 ms interspersed with skipped (jank) or slow frames (40–60 ms). Therefore, FPS is measured as a moving average over 1–5 seconds or as percentiles of the frame time distribution.
On Android, FPS is calculated via Choreographer, which receives a callback from VSync (display synchronization pulse). Each callback corresponds to one frame. If the callback does not arrive — the frame is skipped. Choreographer allows measuring the exact number of frames per second and the number of skipped frames. On iOS, CADisplayLink works similarly — it is called each time the display is ready to render a new frame. The timestamp property contains the exact time of the last frame, and targetTimestamp — the expected time of the next one. The difference between them is the time budget for the current frame.
Swift code demonstrates simple FPS monitoring via CADisplayLink. The frameCount counter increments with each call, and once per second the actual FPS is calculated.
class FpsCounter {
private var displayLink: CADisplayLink?
private var frameCount = 0
private var lastTime = TimeInterval(0)
func start() {
displayLink = CADisplayLink(
target: self,
selector: #selector(countFrame)
)
displayLink?.add(to: .current,
forMode: .common)
}
@objc
private func countFrame() {
frameCount += 1
let now = Date().timeIntervalSince1970
if now - lastTime >= 1.0 {
print("FPS: \(frameCount)")
frameCount = 0
lastTime = now
}
}
}
The 60 FPS (or 60 Hz) standard became established in the industry for several reasons. The first is physiological: the human eye does not distinguish individual frames at frequencies above 50–60 Hz, perceiving them as smooth motion. This threshold is called Critical Flicker Fusion (CFF). The second is historical: early cathode ray tubes (CRT) operated at 60 Hz in the US (NTSC) and 50 Hz in Europe (PAL). Modern LCD displays inherited this frequency. The third is engineering: for UI animations, 60 FPS provides sub-millisecond touch response latency, which is critical for text input, scrolling, and dragging.
For mobile developers, 60 FPS is not just a recommendation but a strict budget of 16.6 ms per frame. This budget is divided among all rendering phases: Input (1–2 ms), Animation (2–3 ms), Layout (3–5 ms), Draw (3–5 ms), and Swap (1–2 ms). If any phase exceeds its sub-budget, the frame may not fit within 16.6 ms. Google Android Performance recommends staying within 12–14 ms for frame preparation, leaving 2–4 ms of headroom for system interruptions (GC, background threads). According to Firebase Performance, applications with an average FPS below 52 and P99 FPS below 30 receive 35% more performance complaints in Google Play reviews.
FPS and Frame Time are two sides of the same metric, and it is important not to confuse them. FPS is speed, Frame Time is latency. At 60 FPS, each frame takes 16.6 ms. At 30 FPS — 33.3 ms. But FPS is a non-linear metric: a drop from 60 to 30 FPS means frame time doubled, while a drop from 30 to 20 means a 1.5x increase. Therefore, profilers show Frame Time rather than FPS — this allows seeing problematic frames instead of an averaged frequency. For example, an average of 55 FPS may hide the fact that 5% of frames have a Frame Time of 50–100 ms — these frames cause Jank but do not significantly affect the average FPS.
When analyzing performance, it is recommended to look not at the average FPS but at the Frame Time histogram. In Android Studio Profiler and iOS Instruments, Frame Time is displayed as a scale where the green zone is up to 16.6 ms (60 FPS), yellow is 16.6–33.3 ms (30–60 FPS), red is more than 33.3 ms (less than 30 FPS). Each red column is a noticeable lag for the user. A practical rule: P95 Frame Time (95% of frames fit within X ms) is a more reliable metric than average FPS. If P95 Frame Time exceeds 32 ms (30 FPS), the application feels sluggish even with an average FPS of 50.
A Kotlin function for converting an array of frame times to FPS with percentiles. It returns not only the average FPS but also P50, P90, and P99 for detailed analysis.
data class FpsReport(
val average: Float,
val p50: Float,
val p90: Float,
val p99: Float
)
fun List<Long>.toFpsReport(): FpsReport {
val fpsValues = this.map { ms ->
if (ms > 0) 1000f / ms else 0f
}.sorted()
return FpsReport(
average = fpsValues.average().toFloat(),
p50 = fpsValues[fpsValues.size / 2],
p90 = fpsValues[(fpsValues.size * 90 / 100)],
p99 = fpsValues[(fpsValues.size * 99 / 100)]
)
}
Modern mobile devices with 90, 120, and 144 Hz displays impose new requirements on FPS. If an application delivers 60 FPS on a 120 Hz display, the user sees micro-stutters because every second screen refresh cycle receives the same frame. To maintain 120 FPS, the per-frame budget shrinks from 16.6 to 8.3 ms — requiring twice as efficient rendering code. According to Android developers (Google I/O 2023), achieving stable 120 FPS requires: avoiding allocations in the Draw cycle, minimizing the number of Views in the hierarchy (under 80), ditching heavy drawables in favor of VectorDrawable, and using surfaceView for complex graphics.
The situation is similar on iOS: iPhone Pro with ProMotion (120 Hz) requires twice as many frames, but the time per frame is halved. Apple notes that not all animations need to run at 120 FPS — Core Animation automatically lowers the frame rate for static or slowly changing elements. However, scrolling, gesture animations, and transitions must deliver 120 FPS for a “silky” feel. The main problems when transitioning from 60 to 120 FPS: increased power consumption (25–40% for GPU), device heating, and throttling — when the frame rate drops due to overheating. It is recommended to implement a fallback mechanism: if Frame Time consistently exceeds 8.3 ms, programmatically lower the target frame rate to 60 FPS rather than waiting for system throttling.
Java code for Android determines whether the device can support 120 FPS and switches the rendering mode. Display.getMode is used to determine supported refresh rates.
class FpsModeSwitcher {
static boolean canDo120Fps(Activity activity) {
Display display = activity.getWindowManager()
.getDefaultDisplay();
for (Display.Mode mode : display.getSupportedModes()) {
if (mode.getRefreshRate() >= 120f) {
return true;
}
}
return false;
}
}
Optimizing FPS requires a systematic approach, starting with profiling and ending with refactoring problem areas. The first step is to measure current FPS using a profiler. The second step is to find frames that exceed the budget. On Android, this can be done via GPU Profiling or Perfetto. On iOS — Instruments with the Core Animation template. The third step is to eliminate the causes: reduce overdraw, decrease View hierarchy depth, replace the layout phase with ConstraintLayout, add ViewHolder Recycling, move heavy computations to a background thread.
Specific FPS optimizations include: Frame Pacing — a mechanism that evenly distributes time between frames to avoid “bursts” of fast and slow frames. On Android, Choreographer.FrameCallback with a fixed interval allows implementing Frame Pacing. On iOS, CADisplayLink.preferredFrameRateRange does the same. The second method — Triple Buffering: the system uses three buffers instead of two, allowing the GPU to start rendering the next frame without waiting for the previous one to be released. Android automatically enables Triple Buffering when needed, but for iOS the developer can explicitly request it via CAMetalLayer. The third — Texture Caching: caching bitmaps in GPU memory to avoid reloading them every frame.
A Kotlin example demonstrates implementing Frame Pacing with a fixed interval of 16.6 ms. All callbacks arrive at a uniform interval, even if the system is delayed.
class PacedFrameRenderer {
private val targetDelta = 16_666_666L // 16.6 ms (60 FPS)
private var lastFrameTime = 0L
private val frameCallback =
Choreographer.FrameCallback { frameTimeNanos ->
val delta = frameTimeNanos - lastFrameTime
if (delta >= targetDelta) {
onFrame(delta)
lastFrameTime = frameTimeNanos
}
Choreographer.getInstance()
.postFrameCallback(this)
}
private fun onFrame(delta: Long) {
// frame rendering
}
}
Frequently Asked Questions
60 FPS is a comfortable level for mobile applications. The difference between 60 and 120 FPS is noticeable only on high refresh rate displays during fast animations (scrolling, dragging). Below 30 FPS — discomfort.
FPS = 1000 / FrameTime (ms). If Frame Time = 16.6 ms, FPS = 60. If Frame Time = 33.3 ms, FPS = 30. It is recommended to monitor Frame Time rather than FPS, as it shows problematic frames.
During scrolling, the system calls Layout and Draw for each new list item. If Views are complex, Layout is not cached, or heavy drawables are used — Frame Time increases and FPS drops. The solution is ViewHolder recycling and a flat hierarchy.
Use Instruments with the Core Animation template (displays FPS in real time). For programmatic measurement — CADisplayLink with frame counting per second. For production — MetricKit with the MXAnimatoryMetric metric.
Triple Buffering uses three buffers instead of two, allowing the GPU to start rendering the next frame before the current VSync completes. This smooths out peak loads and improves FPS stability but adds one frame of latency.
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