How do you optimize long lists in Flutter?
Short Answer
Use `ListView.builder` (or `SliverList`) instead of building all items eagerly, always provide `key`s for items that can reorder, and avoid expensive work inside the `itemBuilder`.
`ListView(children: [...])` builds every item immediately, even ones far off-screen — fine for a handful of items, disastrous for hundreds. `ListView.builder` instead lazily builds only the items that are visible (plus a small cache window), calling your `itemBuilder` on demand as the user scrolls.
Beyond that: give list items stable `Key`s (e.g. `ValueKey(item.id)`) so Flutter can correctly match elements when the list reorders, instead of rebuilding everything from scratch. Avoid doing image decoding, JSON parsing, or other expensive work directly inside `itemBuilder` — precompute it before passing data into the list. For extremely long or complex lists, consider `ListView.separated` for dividers without manual index math, or slivers for mixed scrollable content.
Code Example
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id),
title: Text(item.title),
);
},
)Common Mistakes
- ×Using `ListView(children: items.map(...).toList())` for a long or unbounded list.
- ×Missing `key`s on reorderable list items, causing visual glitches or wrong-item animations.
Related Questions
How do you avoid unnecessary widget rebuilds in Flutter?
Scope state narrowly (so only the widgets that depend on it rebuild), use `const` constructors wherever possible, and split large build methods into smaller widgets so a change in one part doesn't force the whole tree to re-render.
How does Flutter manage memory, and how do you avoid leaks?
Dart uses automatic garbage collection, so you don't free memory manually — leaks in Flutter almost always come from long-lived objects (controllers, streams, listeners) holding references that prevent disposed widgets' resources from being collected.