Flutter apps are fast by default. Flutter apps with eight nested ListViews, no const constructors, and decoded 4K JPEGs in every cell are not. This guide covers the performance work that matters in real Flutter mobile apps — the techniques we use to keep frame times under 16ms and cold starts under a second.
Establish a Baseline
You cannot fix what you do not measure. Run on a physical mid-tier Android device, never the simulator. Profile with flutter run --profile — debug builds are 5-10x slower and will mislead you.
The Frame Budget
You have 16ms to produce a frame at 60fps, or 8ms at 120fps. Within that budget Flutter must:
- Run animation tickers
- Build the widget tree
- Layout
- Paint
- Composite
If any phase exceeds its share, the frame janks. DevTools Performance tab shows you which phase is the culprit.
Build Phase Optimizations
Use const Everywhere You Can
Const widgets are reused across rebuilds. Single biggest free win in Flutter:
// Bad
return Padding(padding: EdgeInsets.all(8), child: Text('hi'));
// Good
return const Padding(padding: EdgeInsets.all(8), child: Text('hi'));
Hoist Stateless Subtrees
If a subtree never depends on changing state, declare it as a const field outside build. Lints like prefer_const_constructors catch most cases.
Narrow BlocBuilder / Consumer / Selector
Wrap the smallest possible subtree in your reactive widget. A Consumer at the root rebuilds the entire screen on every notify.
List Performance
- Always use
ListView.builderfor unbounded lists, neverListView(children: [...]) - Provide
itemExtentwhen items are uniform — saves layout work - Use
cacheExtentwisely; too large bloats memory - For grids,
GridView.builderwithSliverChildBuilderDelegate constinside the builder where possible
Image Pipeline
Images are the #1 source of memory and jank:
- Use
cached_network_imagewithmemCacheWidthandmemCacheHeight - Resize on the server — never download a 4000px image to render at 200px
- Prefer modern formats: AVIF, WebP
- Wrap heavy off-screen images in
RepaintBoundaryto isolate layers
CachedNetworkImage(
imageUrl: url,
memCacheWidth: 400,
memCacheHeight: 400,
placeholder: (_, __) => const ColoredBox(color: Color(0xFFEAEAEA)),
);
Animations
- Prefer implicit animations (
AnimatedContainer,TweenAnimationBuilder) — they batch with the framework - Use
RepaintBoundaryaround animated subtrees so siblings do not repaint - Avoid
setStateon every frame — drive animations fromAnimationControllerwithAnimatedBuilder - Profile with the GPU and Raster timelines, not just the UI thread
Cold Start
First-frame time is the most visible metric to users. Optimize:
- Defer non-essential plugin initialization — Firebase Performance tracks first-launch precisely
- Lazy-load heavy isolates and image decoders
- Splash screen in native code (Android Splash Screen API, iOS LaunchScreen)
- Tree-shake icons (
flutter build --tree-shake-icons) - Enable deferred components for rarely used screens
Memory
- Watch retained memory in DevTools after navigating away from large screens
- Dispose controllers, streams, listeners
- Cap the image cache:
PaintingBinding.instance.imageCache.maximumSizeBytes = 100 << 20 - Avoid keeping
BuildContextbeyond the current frame
Background Work and Isolates
JSON parsing of large payloads, image processing, and crypto belong on isolates:
final users = await compute(parseUsers, jsonString);
For sustained workloads, use long-lived isolates with Isolate.spawn and message passing. flutter_isolate simplifies plugin access from isolates.
App Size
flutter build appbundle --analyze-sizereveals the worst offenders- Drop unused fonts and icon sets
- Strip debug symbols from release builds
- Use SVGs where rasters are not needed
Profiling Workflow
- Reproduce the issue in profile mode on a real device
- Open DevTools Performance tab; record a session
- Inspect frames over budget; jump to the build/raster culprit
- Fix one thing at a time; re-measure
- Add a regression benchmark for serious wins
Tools to Know
- Flutter DevTools: build, layout, repaint rainbow, memory snapshots
- Observatory: live VM and isolate inspection
- Android Studio Profiler: native CPU/memory/network
- Xcode Instruments: time profiler, allocations, energy
Conclusion
Flutter performance is a discipline, not a one-time pass. Start with const, ListView.builder, and image sizing — those three fix most apps. Then profile, fix the worst offender, repeat. Bake performance budgets into CI. The result is a Flutter mobile app that feels fast on a five-year-old Android — which is the only test that matters.