← Flutter & Dart

Advanced Flutter

What's the difference between `ListView(children: [...])` and `ListView.builder`, and why does it matter?

Show answer — try answering out loud first

ListView with children builds all the items at once, even those not visible on screen. ListView.builder builds items on demand (only the visible ones and a few nearby), reusing them as you scroll. For long lists or lists of unknown size, always use ListView.builder; it's far more efficient in memory and performance.

  • ListView(children: [...]): you pass it a fixed list of already-created widgets. Flutter instantiates all of them. It's fine for a few things (5-10 fixed items).
  • ListView.builder: you give it an itemCount and an itemBuilder that creates each item when needed. Flutter only builds the ones that are (or are about to be) on screen, and recycles the ones that leave. This is called lazy loading.

If you have 10,000 items and use the first form, you create 10,000 widgets all at once: the app consumes a lot of memory and can freeze. With builder, only the visible ones exist.

import 'package:flutter/material.dart';

// Bad for long lists: creates ALL items at once
class FixedList extends StatelessWidget {
  const FixedList({super.key});
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: const [
        ListTile(title: Text("Item 1")),
        ListTile(title: Text("Item 2")),
        ListTile(title: Text("Item 3")),
      ],
    );
  }
}

// Good for long lists: creates items on demand
class LargeList extends StatelessWidget {
  final List<String> items;
  const LargeList({super.key, required this.items});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        // Only called for the visible items (and a few nearby)
        return ListTile(title: Text(items[index]));
      },
    );
  }
}

Using ListView(children: ...) with a long list generated by a map or a for. Even though it works, it builds everything at once and wastes memory. Another related mistake: putting a ListView inside a Column without constraining its height, which causes layout errors ("unbounded height"); in that case it's better to wrap it in Expanded or use shrinkWrap.

"ListView with children creates all the items at once, and it's only good for short, fixed lists. ListView.builder creates them on demand with itemCount and itemBuilder: Flutter builds only the visible ones and recycles those that leave the screen, which is lazy loading. For long lists or lists of unknown size I always use builder, because with the other form a list of thousands of items would consume a huge amount of memory and freeze the app."

Quick challenge

You need to show a list of 5,000 products coming from an API. Which widget do you use and why?

See answer

ListView.builder. With 5,000 items, ListView(children: ...) would create all 5,000 widgets at once, wasting a lot of memory and risking jank. ListView.builder only builds the ones on screen (and a few around them), recycling them as you scroll, so consumption stays low no matter how many products there are.