Explain ListView vs ListView.builder

Medium PriorityAsked in ~65% of Flutter interviews

2 min read

Widgets & UI

ConstructorWhen children are builtWhen to use
ListView(children: ...)All at onceStatic, small lists (~20 items, no dynamic data)
ListView.builder(itemBuilder: ...)Lazily, near the viewportMost lists — anything dynamic or potentially long
ListView.separated(...)Lazily, with separatorsLists with dividers, sectioned UI
ListView.custom(...)Lazy, fully custom delegateAdvanced cases (chunked loading, custom indexing)

Lazy variants use a SliverChildBuilderDelegate under the hood — only items in the viewport plus the cacheExtent are built; the rest are discarded.


Code in action

// Tiny static list — fine
ListView(
  children: const [
    ListTile(title: Text('Profile')),
    ListTile(title: Text('Settings')),
    ListTile(title: Text('Log out')),
  ],
);

// Long / dynamic list — use builder
ListView.builder(
  itemCount: messages.length,
  itemBuilder: (ctx, i) => MessageTile(messages[i]),
);

// With separators
ListView.separated(
  itemCount: items.length,
  itemBuilder: (ctx, i) => Tile(items[i]),
  separatorBuilder: (_, __) => const Divider(height: 1),
);

// Pagination / infinite scroll
ListView.builder(
  itemCount: items.length + 1,
  itemBuilder: (ctx, i) {
    if (i == items.length) {
      _loadMore();
      return const Center(child: CircularProgressIndicator());
    }
    return ItemTile(items[i]);
  },
);

When to use which

SituationUse
Fewer than ~20 static itemsListView(children: [...])
Dynamic list from a backend / stateListView.builder
Dividers between rowsListView.separated
Mixed content (header + list + footer)CustomScrollView with SliverList + SliverToBoxAdapter
Horizontal listAdd scrollDirection: Axis.horizontal (works on both)
Pull-to-refreshWrap in RefreshIndicator

Common mistakes to avoid

// ❌ Building giant lists eagerly
ListView(
  children: bigList.map(buildHeavyTile).toList(),     // 5000 widgets at once
);
// ✅ ListView.builder

// ❌ Forgetting itemCount when using builder
ListView.builder(itemBuilder: (ctx, i) => Tile(...));  // infinite!

// ❌ ListView inside a Column without a bounded height
Column(children: [const Header(), ListView(...)]);     // 💥 unbounded height
// ✅ Wrap ListView in Expanded, OR use shrinkWrap: true (avoid for big lists)

// ❌ Mixing shrinkWrap + nested scrolling for performance
ListView(shrinkWrap: true, physics: NeverScrollableScrollPhysics(), ...);
// fine for tiny static lists; for long lists use Slivers / CustomScrollView

// ❌ No keys on stateful list items that can reorder
// (See Q21 — use ValueKey(item.id))

Interview follow-ups

  1. What does cacheExtent do on a ListView? It's the amount of off-screen pixels Flutter keeps built ahead/behind the viewport. Higher cache extent = smoother scrolling but more memory; lower = lighter but more rebuild flicker. The default is sensible for most cases.

  2. What's the difference between ListView.builder and SliverList? ListView.builder is a scroll view with a single sliver. SliverList is the underlying primitive — use it directly inside CustomScrollView when you want to combine multiple scrollable regions (e.g., SliverAppBar + SliverList + SliverGrid).

  3. When would you use shrinkWrap: true and what's the cost? When a ListView must size itself to its children (e.g., inside a Column). The cost is that Flutter has to build all children to measure them, defeating laziness. Only acceptable for short lists.

  4. How do you implement infinite scroll / pagination? Detect when you're near the end (either with an extra "loading" tile at the bottom or via a ScrollController listener that triggers when pixels >= maxScrollExtent - threshold) and fetch the next page, then setState / update your state store.


How helpful was this content?

Please sign in to rate this article.