Performance View: frame analysis and jank tracing in profile mode

Medium PriorityAsked in ~50% of mid-level interviews

3 min read

DevTools

Bar / trackWhat it represents
UI thread barsTime spent on build / layout / paint per frame
Raster thread barsTime spent rasterising (GPU work, compositing)
Red barDropped frame — total exceeded budget
Yellow barFrame near the limit
Vertical guideThe 16.67ms / 8.33ms budget line

A frame can drop because the UI thread was too slow (build/layout) OR the raster thread was too slow (paint/compositing). Click a red frame → DevTools shows the breakdown.


Workflow — the frame analysis loop

# 1. Always profile mode
flutter run --profile
  1. Open DevTools → Performance.
  2. Press Record.
  3. Reproduce the laggy interaction (scroll, animation, page transition).
  4. Stop recording.
  5. Find the red bars on the timeline.
  6. Click into one — DevTools shows the Build / Layout / Paint breakdown.
  7. If UI thread is the culprit → CPU Flame Chart (Q61) to find the slow code.
  8. If Raster thread is the culprit → look at the Layers / Raster Cache views.
  9. Fix the hotspot.
  10. Re-run the same recording → confirm the bar is green now.

Common root causes by symptom

SymptomWhere to lookUsual fix
Jank on first appearanceUI thread, especially buildCache derived data; defer heavy work; precacheImage
Jank during scrollUI thread per frame, then rasterListView.builder; lighter item widgets; RepaintBoundary
Jank during animationRaster thread spikesRepaintBoundary around animated subtree; smaller dirty area
Jank on screen rotation / keyboardUI threadAvoid MediaQuery.of high in the tree; use targeted helpers
Jank after a state changeUI thread spike at one frameNarrow rebuild scope (push state down, context.select)
Steady high frame times, no spikesBoth threadsLook for per-frame allocations (GC pressure); image decode on UI thread

Useful tools alongside the Performance view

ToolWhen to flip it on
Performance Overlay (MaterialApp.showPerformanceOverlay: true)On-device, real-time graph of frame times — quickest sanity check
debugProfileBuildsEnabledRecords every widget's build time in the timeline
debugRepaintRainbowEnabledVisualises repaints — pulsing colour = repainting too often
Memory profiler (Q62)If perf issues correlate with growing memory
CPU Flame Chart (Q61)Once you know when it janks, find what code caused it

Common mistakes to avoid

❌ Measuring in debug mode
   Debug adds assertions, layout verification, allocations — 5-10× slower.
   ✅ Always flutter run --profile

❌ Looking at average frame time only
   A 99th-percentile spike is what users feel as jank. Look at the spikes, not the mean.

❌ Optimising before measuring
   "I bet it's the X widget" → spend 2 days → wasn't even that.
   Measure first, then fix.

❌ Optimising without re-measuring
   Confirm the fix actually moved the timeline before declaring victory.

❌ Confusing UI-thread and raster-thread jank
   Build-time fixes don't help GPU-bound jank. Read which thread is red.

❌ Profiling on the host machine
   x86 desktop performance ≠ low-end ARM device performance. Profile on the slowest target device.

Interview follow-ups

  1. What's the difference between UI thread jank and raster thread jank? UI thread runs build/layout/paint recording — anything in Dart code. Raster thread runs the actual GPU rendering. UI-thread jank is fixed by lighter Dart code (cache, defer, push state down). Raster-thread jank is fixed by lighter graphics work (fewer layers, RepaintBoundary, smaller dirty regions).

  2. What is the 16ms budget and where does it come from? 60Hz screens refresh every 16.67ms. To deliver every frame on time, all work (build + layout + paint + raster + compositing) must finish within that window. 120Hz devices halve it to 8.33ms. The numbers come from human visual perception thresholds — anything missing the budget is felt as stutter.

  3. What's a RepaintBoundary and when does it actually help? It gives a subtree its own compositing layer, so a repaint inside it doesn't dirty the parent. Use it around animated or frequently-changing subtrees — charts, video players, custom-painted widgets. Adding boundaries everywhere is not a win; each layer has cost.

  4. You see a red frame but the CPU profile looks light. What now? The bottleneck is probably the raster thread (GPU). Look at the raster thread bar — heavy paint can be caused by lots of saveLayer, large BackdropFilter, oversized images, or alpha-blended overlap. Switching to Impeller (Q58) often fixes shader-compile spikes specifically.

How helpful was this content?

Please sign in to rate this article.