- Published on
Flutter Performance Part 1: Stop Guessing, Start Profiling
- Authors

- Name
- Phat Tran
Let's be honest—we've all been there. You're building a beautiful transaction history screen for your digital banking app. It looks amazing on your emulator, but the moment you hand the test build to the QA team, they complain about a noticeable "stutter" when scrolling through the list of transactions.
Your first instinct might be to randomly wrap things in const, remove animations, or rewrite entire widgets. Stop right there. Stop guessing.
Performance optimization is a science, not a guessing game. The golden rule is: Measure first, optimize later. In this first part of our Flutter Performance series, we’ll look at how to properly diagnose your app's health before writing a single line of optimization code.
1. The Cardinal Rule: Never Test in Debug Mode
This is the most common mistake among Flutter developers. You run your app via your IDE, notice some lag, and immediately panic.
When you run Flutter in Debug Mode, the app is compiled using the JIT (Just-In-Time) compiler. This allows for hot-reloading and extensive debugging, but it comes with a massive performance overhead. Animations will stutter, and heavy calculations will freeze the UI.
Always test performance on a real physical device using Profile Mode. Profile Mode compiles your app using the AOT (Ahead-Of-Time) compiler—the exact same way it will be compiled for the App Store or Google Play—but leaves just enough debugging hooks open for DevTools to monitor performance.
# Run this in your terminal with a real device plugged in
flutter run --profile
2. Reading the Performance Overlay
Once your app is running in Profile Mode, the next step is to turn on the Performance Overlay. You can do this from Flutter DevTools or by pressing p in your terminal.
You'll see two graphs appear on your screen:
- The UI Thread (Top Graph): This represents the Dart code executing on your main isolate. If this graph spikes, it means your Dart code is too heavy. You might be parsing a massive JSON payload of 10,000 transaction records synchronously, or triggering too many widget rebuilds.
- The Raster/GPU Thread (Bottom Graph): This represents the graphics engine turning your widgets into pixels. If this spikes, your Dart code is fine, but the rendering is too complex. You might be overusing expensive effects like
Opacity,ClipPath, or complex shadows on your bank cards.
Note on Impeller: If you are using Flutter 3.10+ on iOS (and increasingly on Android), Flutter uses the Impeller Engine instead of Skia. Impeller pre-compiles shaders, which drastically reduces early "shader compilation jank" (that annoying lag the very first time an animation runs). However, if your Raster graph is still spiking on Impeller, it means you are genuinely pushing too many complex visual layers.
3. The CPU Profiler: Finding the Culprit
So, you saw a red spike on the UI Thread graph while searching for a specific beneficiary in your app. What exactly caused it?
Instead of reading through thousands of lines of code, open Flutter DevTools in your browser and navigate to the CPU Profiler tab. Record a trace while you perform the laggy action on your phone.
Look at the Flame Chart (the visual breakdown of function calls):
- Look for the widest horizontal bars. The width represents time taken in milliseconds.
- Drill down from top to bottom. You might find that a seemingly innocent utility function—like formatting currency
NumberFormat.simpleCurrency().format(amount)inside a largeListView.builder—is taking up 8ms per frame!
Once you have mathematical proof of what is slow, you can fix it with confidence.
Up Next: Putting your UI on a Diet
Now that we know how to identify performance bottlenecks using Profile Mode and DevTools, it's time to fix them. In Part 2 of this series, we'll dive into the UI Thread. We will explore how to tame unwanted rebuilds using const, RepaintBoundary, and modern state management tools like BLoC and Signals.