Web & Mobile · 5 minute read
Flutter Performance: Finding and Fixing the Real Bottlenecks
Flutter performance problems concentrate in three places: rebuilding more of the tree than necessary, lists that construct every item, and image decoding on the wrong thread. Diagnosing on a low-end physical device in profile mode is what separates real bottlenecks from imagined ones.
Flutter performance problems concentrate in a small number of places, and most time spent optimising is spent in the wrong ones. This guide covers finding the real bottlenecks, drawing on FISTA Solutions' web and mobile work.
Where do the problems actually live?
Four causes account for most of what teams find.
| Cause | Symptom |
|---|---|
| Rebuild scope too wide | Jank on any state change |
| Non-lazy list construction | Slow open, high memory |
| Full-resolution image decode | Memory pressure, dropped frames |
| Expensive build methods | Consistent frame overruns |
| Blocking startup work | Slow cold start |
| Unnecessary opacity and clipping | Raster thread cost |
How should you profile?
In profile mode, on a low-end physical device, with the performance overlay on.
Debug builds carry assertions and skip compiler optimisation, so they are dramatically slower in ways that do not reflect production. Profiling in debug mode produces conclusions that are simply wrong.
A high-end phone hides most problems. The device that matters is the cheap one your users actually have, and problems invisible on a flagship are obvious there.
How do you narrow rebuild scope?
By pushing state as far down the tree as it can go, and by isolating the parts that change.
When state lives high in the tree, every change reconstructs everything beneath it. Moving state into the smallest widget that needs it means a change touches a few widgets rather than hundreds.
Const constructors are the other half: a const widget is not rebuilt at all. They cost nothing and are consistently underused.
What do lists require?
Lazy construction, keys where items move, and cheap item widgets.
Builders that construct items as they scroll into view keep cost proportional to what is visible rather than to list length. This matters from a few hundred items onward and is fatal at a few thousand.
Keep item widgets simple. A complex cell rebuilt during a scroll is the usual source of scroll jank, and simplifying the cell helps more than any other change.
How should images be handled?
Decoded at display size, cached, and sized explicitly.
Specifying the decode dimensions means a large photograph shown as a thumbnail consumes thumbnail memory rather than full-resolution memory. Without it, a list of images will exhaust a low-end device.
Use the caching layer for network images rather than fetching repeatedly, and give images explicit dimensions so layout does not shift when they load. See web performance optimization guide.
What makes startup slow?
Everything that happens before the first frame.
Synchronous initialisation, plugin setup, and blocking network calls all delay first paint. Audit what runs before the first screen appears and move anything not strictly required to after it.
Deferred initialisation and lazy loading of features the first screen does not need are the two reliable wins. Measure cold start on a low-end device, since warm start hides the cost.
What about the raster thread?
It is the one people forget, and it is where opacity, clipping, and shadows cost.
Flutter separates the work of building widgets from the work of rasterising them. A frame can be fast to build and slow to raster, and the fixes are entirely different.
Widespread use of opacity layers, clipping with anti-aliasing, and elevation shadows adds raster cost. The performance overlay shows both threads; read both before deciding what to fix.
What are the common mistakes?
Profiling in debug mode. Optimising on a flagship device. State held too high in the tree. Non-lazy lists. Full-resolution image decoding. And ignoring the raster thread entirely.
How do you test it?
Include frame timing in automated testing where possible, and re-measure cold start on every release. Regressions arrive quietly with new dependencies and initialisation code.
Test on the oldest device you claim to support, not the newest.
What does it cost to operate?
Performance work is engineering time, and the first day of it usually returns the most. Diminishing returns arrive quickly once the four common causes are addressed.
Device testing hardware is a modest one-off cost and worth it.
What should you measure?
Frame build and raster times at the ninety-ninth percentile, dropped frame count during scroll, cold start duration on a low-end device, and peak memory during image-heavy screens.
What about AI features in Flutter apps?
Keep inference off the interface thread and stream results rather than waiting. A model call that blocks the frame loop produces exactly the jank this guide is about.
Presentation of streaming output should update at a readable rate rather than on every token, because rebuilding text on each chunk is expensive and looks worse. See streaming UI patterns for AI apps.
When is this the wrong approach?
An app with a few simple screens and no lists does not need performance work. Measure first; the discipline is finding the real bottleneck, not applying every technique.
What should you do first?
Run your app in profile mode on the cheapest device you support and scroll your longest list. Whatever you see there is the work worth doing.
How FISTA Solutions helps
FISTA Solutions builds and operates production systems through web and mobile, AI enablement, and staff augmentation: profiling done in profile mode on low-end hardware, rebuild scope narrowed before anything else is optimised, decisions documented with their reasoning, and handover that leaves your team able to maintain what was delivered. The record is 150+ projects for 50+ companies across 12+ countries.
To scope this work, message FISTA on WhatsApp, or read react native performance guide.
Share-ready article cover
Download the generated social format.
Clear answers
Questions raised by this field note.
Straightforward guidance for evaluating scope, fit, and the next step.
01Why must you profile in profile mode?
Debug builds are substantially slower by design, with assertions and no compiler optimisation. Numbers taken in debug mode are meaningless, and they usually point at the wrong problem entirely.
02What is rebuild scope?
How much of the widget tree reconstructs when state changes. A state object high in the tree rebuilds everything below it, which is the most common cause of avoidable frame cost in Flutter apps.
03How should long lists be built?
With a builder that constructs items lazily as they scroll into view. Building every item up front costs memory and time proportional to list length, which degrades badly past a few hundred entries.
04Why do images cause problems?
Because decoding happens at full source resolution unless told otherwise. A large photograph displayed as a thumbnail consumes the memory of the full image, and several of those exhaust a low-end device.
05What dominates startup time?
Work done before the first frame â synchronous initialisation, plugin registration, and blocking network calls. Moving anything not needed for the first screen to after first paint is usually the largest available win.
Continue exploring
Related capabilities
Start with the hard problem
Need the outcome owned, not merely analyzed?
Tell us where delivery is constrained. Weâll map the fastest credible path from intent to verified production.