← All articles
Flutter10 min read

Flutter Performance: how to find and fix jank, slow lists and memory problems

Why Flutter apps stutter and what to do about it — rebuild scope, list building, image sizing, expensive widgets, and how to measure with DevTools instead of guessing.

FlutterPerformanceDevTools

Flutter apps are fast by default. When one stutters, the cause is usually not the framework but something the app asks it to do on every frame. This guide covers how to measure the problem properly and the fixes that account for most real-world jank.

What “smooth” actually means

At 60Hz the device has about 16 milliseconds to build, lay out and paint a frame. On a 120Hz screen, about 8. Miss the budget and a frame is dropped, which the eye reads as a stutter. Two separate threads matter: the UI thread runs your Dart code, and the raster thread turns the result into pixels. Knowing which one is over budget changes the fix entirely.

Measure before changing anything

Guessing at performance wastes days. Three tools cover most cases.

Always profile in profile mode

Debug builds are far slower than release builds and will mislead you. Run flutter run --profile on a physical device. Never judge performance on an emulator or in debug mode.

DevTools Performance view

Record while reproducing the stutter, then look at the frame chart. Tall bars on the UI track mean your Dart work is too slow — usually building too many widgets or doing computation in build(). Tall bars on the raster track mean painting is too expensive — usually shadows, blurs, opacity or clipping.

Rebuild counters

Turn on “Track widget rebuilds” in DevTools. If a widget rebuilds when unrelated state changes, that is a scope problem, and it is the most common cause of sluggish screens.

Fix 1: shrink the rebuild scope

The most frequent mistake is rebuilding a large subtree to update a small piece of it. The fix is to move the listener as close to the value as possible.

// Rebuilds the whole page on every change
Consumer<CartModel>(
  builder: (_, cart, __) => Scaffold( /* entire page */ ),
)

// Rebuilds only the badge
Scaffold(
  appBar: AppBar(actions: [
    Consumer<CartModel>(
      builder: (_, cart, __) => Badge(count: cart.items.length),
    ),
  ]),
  body: const ProductList(), // const: never rebuilt
)

Note the const. A const widget is created once and skipped on subsequent rebuilds. Marking leaf widgets const wherever possible is free performance, and the analyzer can enforce it with the prefer_const_constructors lint.

Fix 2: build lists lazily

ListView(children: [...]) builds every child immediately, even the thousand below the fold. ListView.builder builds only what is visible.

  • Use ListView.builder or SliverList for any list that is not trivially short.
  • Give items a stable key so Flutter can reuse elements when the list reorders.
  • Avoid shrinkWrap: true on long lists; it forces measuring every child.
  • Do not nest scrollables. Use CustomScrollView with slivers instead.
  • Keep item widgets cheap. A list row that builds a complex layout, formats dates and parses strings will stutter no matter how lazily it is built.

Fix 3: size images to the screen, not the server

Images are the biggest memory consumer in most apps, and a common cause of raster-thread jank. A 4000px photo displayed in a 100px avatar still decodes at full size unless told otherwise.

Image.network(
  url,
  cacheWidth: 200,   // decode at the size actually displayed
  cacheHeight: 200,
)

Better still, request an appropriately sized image from the backend or CDN. Use a caching image package so scrolling back up does not re-download, and always provide width and height so layout does not shift when the image arrives.

Fix 4: watch the expensive paint operations

These are cheap individually and costly in a scrolling list:

  • Opacity. Opacity triggers a saveLayer. Prefer AnimatedOpacity on small subtrees, or fade with a colour where possible.
  • Clipping. ClipRRect on every card is measurable. A BoxDecoration with borderRadius is usually cheaper.
  • Shadows and blur. BackdropFilter is the most expensive common widget in Flutter. One is fine; one per list item is not.
  • Large Stacks with overlapping transparency.

Fix 5: keep work out of build()

build() can run many times per second. It should assemble widgets and nothing else. Move these out:

  • JSON parsing, sorting and filtering of large lists
  • Date and number formatting in a loop
  • Creating controllers or streams — those belong in initState
  • Regex compilation

For genuinely heavy computation, move it off the UI thread entirely with compute() or an isolate. Parsing a large API response is a good candidate.

Fix 6: animate without rebuilding

An AnimationController that calls setState on every tick rebuilds the subtree sixty times a second. AnimatedBuilder with a child passed in rebuilds only the animating wrapper and reuses the child.

AnimatedBuilder(
  animation: controller,
  child: const ExpensiveContent(), // built once
  builder: (_, child) => Transform.rotate(
    angle: controller.value * 6.28,
    child: child,
  ),
)

App size and startup

Perceived performance starts before the first frame.

  • Build an app bundle so users download only what their device needs, and --split-per-abi if distributing APKs directly.
  • Audit dependencies. Each package adds code, and some pull in large transitive trees.
  • Compress and right-size bundled assets. Shipping 2MB PNGs that display at 200px is common and avoidable.
  • Do not block the first screen on network calls. Render the layout, then fill it in.

A practical order of attack

  1. Reproduce the jank on a real device in profile mode.
  2. Check DevTools: is the UI thread or the raster thread over budget?
  3. UI thread → narrow rebuild scope, move work out of build(), add const.
  4. Raster thread → look for opacity, clipping, blur and oversized images.
  5. Re-measure. Keep only the changes that show up in the numbers.

Most apps do not need exotic optimisation. Narrow rebuilds, lazy lists and correctly sized images account for the large majority of real performance problems.

Read next