Flutter Performance Optimization

Users judge an app by how smooth it feels. A stutter during scrolling or a slow screen change makes an app seem broken, even when every feature works. Flutter aims for 60 frames per second, which gives each frame about 16 milliseconds. Many modern phones run at 90 or 120 frames per second and allow even less time.

The Frame Budget


  One frame = 16 ms budget (60 fps)

  ├──── build ────┼── layout ──┼── paint ──┤
  │     5 ms      │    4 ms    │   3 ms    │  = 12 ms  ✔ smooth

  ├─────────── build ──────────┼─ layout ─┼─ paint ─┤
  │            20 ms           │   4 ms   │  3 ms   │  = 27 ms  ✘ dropped frame

A dropped frame appears on screen as a brief freeze called jank. Optimization work keeps every frame inside its budget.

Measure Before You Change Anything

Guessing wastes time. Measure first.

  • Run the app in profile mode on a real phone: flutter run --profile.
  • Open the Performance view in DevTools and look for red frames.
  • Turn on the performance overlay with showPerformanceOverlay: true in MaterialApp.

Debug mode runs much slower than release mode, so never judge speed there.

Use const Widgets

A const widget is built once and reused forever. Flutter skips it during rebuilds.


// Better
const Text('Welcome');
const SizedBox(height: 16);
const Icon(Icons.home);

Add const wherever the analyzer suggests it. This habit costs nothing and removes thousands of needless rebuilds.

Rebuild Less

Calling setState rebuilds the whole widget that owns it. Large widgets that rebuild every second waste effort. Keep changing parts small and separate.


  Heavy: one big screen rebuilds when the counter changes
  ┌───────────────────────────────┐
  │ Header   List   Footer  Count │  ← all rebuild
  └───────────────────────────────┘

  Light: only the counter widget rebuilds
  ┌───────────────────────────────┐
  │ Header   List   Footer [Count]│  ← only [Count] rebuilds
  └───────────────────────────────┘

Move the changing part into its own small widget with its own state. Tools such as BlocBuilder, Consumer, and ValueListenableBuilder help rebuild narrow sections.

Build Lists Lazily

Use ListView.builder and GridView.builder for long collections. These widgets create only the visible rows. A Column inside a SingleChildScrollView builds every child at once, which hurts performance for large lists.

Keep build() Light

Flutter may call build many times per second. Avoid sorting a big list, parsing JSON, or reading files inside it. Calculate once and store the result.


// Slow: sorts on every rebuild
Widget build(BuildContext context) {
  final sorted = items..sort();
  return ListView( /* ... */ );
}

// Better: sort once in initState or when data changes

Move Heavy Work Off the Main Thread

Dart runs UI code on one thread. A long calculation freezes the screen. The compute function runs a task in a separate isolate, a worker with its own memory.


import 'package:flutter/foundation.dart';

List<int> sortLargeList(List<int> data) {
  data.sort();
  return data;
}

final result = await compute(sortLargeList, bigList);

  Main isolate (UI)            Worker isolate
  ┌──────────────┐  send data  ┌──────────────┐
  │ draws frames │ ──────────→ │ heavy work   │
  │ stays smooth │ ←────────── │              │
  └──────────────┘   result    └──────────────┘

Parsing a large JSON response is a classic case for compute.

Handle Images Carefully

  • Resize images on the server instead of loading a 4000-pixel photo into a 100-pixel thumbnail.
  • Set cacheWidth or cacheHeight on Image to decode a smaller version.
  • Use the cached_network_image package to avoid downloading the same picture twice.
  • Prefer compressed formats such as WebP.

Limit Expensive Effects

Widgets such as Opacity, ClipRRect, and BackdropFilter force extra drawing passes. Use them sparingly, and prefer a color with an alpha value over an Opacity widget around a large subtree.

Wrap an animated section in RepaintBoundary to keep its repainting from affecting its neighbors.

Use Keys for Reorderable Lists

Give list items a ValueKey tied to a unique id. Flutter matches each widget to its data after a reorder and avoids rebuilding everything.

App Size and Startup

  • Remove unused packages from pubspec.yaml.
  • Build with flutter build apk --split-per-abi or an app bundle for smaller downloads.
  • Delay loading heavy features until the user opens them.

Quick Checklist

SymptomLikely CauseRemedy
Janky scrollingNon-lazy list or heavy itemsUse builder constructors
Slow screen openWork inside buildMove work to initState or compute
High memory useLarge imagesResize and cache
Whole screen flickers on tapWide setStateSplit into smaller widgets

Practice Ideas

Take a scrolling list of one thousand rows and record a profile run in DevTools. Replace a plain Column with ListView.builder and compare the frame chart. Move a heavy JSON parse into compute and watch the red frames disappear. Measure again after each change, so you learn which fixes matter most.