Why should a Flutter team never judge a janky screen's performance in debug mode, and what should they do instead?
answer
- JIT and asserts on every frame
- slowdown is not uniform
- emulators are not representative
- flutter run --profile on a device
- gate with kDebugMode, not kReleaseMode
basics
~20 sDebug builds run JIT-compiled code with assertions and debug-only work on every frame, so their timings are unrepresentative and unevenly distorted. Reproduce the jank with flutter run --profile on a physical device, then measure and optimize there.
solid answer
~40 sDebug mode is tuned for fast edit cycles: it runs JIT-compiled code for hot reload, executes the framework's many `assert` checks on every frame, and keeps service extensions and debugger hooks on. The slowdown is not uniform, so debug timings can blame the wrong code, and emulators or the iOS Simulator add their own distortion. The Flutter docs say to measure performance in profile mode on an actual device. So run `flutter run --profile` on a physical phone, ideally a low-end one, reproduce the interaction, read the results in DevTools, and only then optimize and re-measure in the same mode. Gate debug-only code with `kDebugMode` or `assert`, not `kReleaseMode`, so profile runs the same code as release.
go deeper
Remember the rule: never trust performance numbers from a debug build, measure in profile mode on a real phone.
Explain why debug is slow, JIT, assertions and debug-only work, and what profile mode keeps so you can still measure.
Show the full routine with a low-end physical device, profile builds, repeated measurements and re-measuring after each fix, and spot kReleaseMode gates that skew profile.
Define performance budgets that are measured the same way every time, in profile on reference devices, so regressions are comparable across releases.
## The scenario A teammate says a screen is janky. They open it on the Android emulator with `flutter run`, scroll, see stutter and frame times well above budget, and start rewriting widgets. Before anyone optimizes anything, the right question is: **what build mode, on what device?** ## Why debug-mode numbers mislead Debug mode is built for fast edit cycles, not speed. The Flutter docs say plainly that performance can be janky in debug mode and that performance should be measured in profile mode on an actual device. The reasons: - **JIT instead of AOT.** Debug builds run code in the Dart VM with just-in-time compilation to support hot reload. Profile and release builds ship ahead-of-time compiled machine code. - **Assertions everywhere.** The framework is full of `assert` checks that validate layout, painting and state. They run on every frame in debug and are compiled out in profile and release. - **Debug-only work.** Code gated by `kDebugMode` or inside asserts, service extensions and debugger hooks all add overhead. - **The wrong device.** Emulators and simulators do not behave like a phone's CPU and GPU. The iOS Simulator only runs debug builds at all, and the docs describe emulators and simulators as unrepresentative of real performance. The effect is not a constant slowdown you can divide out: different code paths slow down by different amounts, so debug numbers can point at the wrong culprit. ## What to do instead 1. **Use a physical device**, ideally a lower-end one that matches your users. 2. **Build in profile mode**: `flutter run --profile`. It compiles ahead of time like release but keeps tracing and lets DevTools connect. 3. **Reproduce the same interaction** and look at the results with the performance tools (reading frame charts is a separate skill). 4. **Only then optimize**, and re-measure in profile mode after each change. ## What profile mode still gives you | Available in profile | Not available in profile | |---|---| | Tracing and timeline events | Hot reload and hot restart | | DevTools connection | Assertions | | Performance overlay service extension | Debug painting and other debug-only extensions | | Widget build profiling extensions | Source-level debugging | That split is deliberate: profile keeps what you need to **observe** performance and drops what **distorts** it. ## Traps inside the measurement - **Code that behaves differently by mode.** The `kReleaseMode` documentation warns that gating logic on `kReleaseMode` introduces differences between release and profile builds, making performance testing less representative. Gate debug-only work with `kDebugMode` or `assert` instead, so profile and release run the same code. - **The web.** Profile builds of a Flutter web app use `dart2js` without minification, and DevTools cannot connect to them; use the browser's own performance tools. - **First runs.** Measure a few repetitions, not just the first scroll after launch. ## How to say it in an interview Name the rule, give the reasons, and describe the fix: "Debug mode is JIT-compiled and full of assertions, so its timings are not representative; I would reproduce the jank in profile mode on a real device, confirm it there, and only then optimize." Pointing out that the emulator itself is part of the problem shows production experience. ## A checklist before anyone reports a performance problem - The build was made with `--profile`, not the default debug mode. - It ran on a physical device representative of real users, not an emulator or the iOS Simulator. - The interaction was repeated several times, not measured once right after launch. - No debug-only work, such as verbose logging behind `!kReleaseMode`, runs in the profile build. - The same steps are written down, so the measurement can be repeated after the fix in the same mode on the same device.
- Why does the kReleaseMode documentation advise gating debug-only code with kDebugMode or assert instead?Code gated on `kReleaseMode` runs in profile but not in release, so profile builds execute work that users never do. That makes profile measurements less representative. With `kDebugMode` or `assert`, the extra work exists only in debug, and profile matches release.
- Can you profile a Flutter web app with DevTools in profile mode?No. The docs state that DevTools cannot connect to a Flutter web app running in profile mode. The web profile build is `dart2js`-compiled and tree-shaken but not minified, so use the browser's own performance tools to record timeline events.
saying these in an interview costs you the question
- Debug mode is uniformly slower, so you can divide its timings by a factor.
- Profiling on the emulator is fine if the development machine is fast.
- Release mode is best for profiling because it is fastest.
- Profile mode still runs assertions to help find the jank.
- Wrapping extra logging in if (!kReleaseMode) keeps profile builds representative.