Skip to content
Blog

GridView.builder in Flutter: Efficient Lazy Grids at Any Size

Build efficient lazy grids in Flutter — choosing delegates, computing childAspectRatio, responsive column counts, sliver integration, and performance pitfalls.

Published on • November 2, 2026

AI Assistant

Grids are the default layout for galleries, catalogs, and dashboards, and GridView.builder is the variant you should reach for because it only constructs the cells on screen (docs.flutter.dev). Picking the right delegate and aspect ratio is most of the work.

GridView.builder builds only what is visible

itemCount + itemBuilder let Flutter create and dispose cells lazily as the user scrolls — never build your own List<Widget> for a grid of thousands of items.

GridView.builder(
  padding: const EdgeInsets.all(12),
  itemCount: products.length,
  itemBuilder: (ctx, i) =>
      Card(child: Image.network(products[i].url, fit: BoxFit.cover)),
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12,
    childAspectRatio: 3 / 4,
  ),
);

FixedCrossAxisCount vs MaxCrossAxisExtent

childAspectRatio is width / height: a cell 160px wide that must be 200px tall needs 0.8, not 1.25. Use FixedCrossAxisCount when the column count is a design decision, and MaxCrossAxisExtent when the width should decide it.

// Always exactly 3 columns:
const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 3, crossAxisSpacing: 8, mainAxisSpacing: 8,
  childAspectRatio: 1,
)
// Cells never wider than 180px — columns adapt to width:
const SliverGridDelegateWithMaxCrossAxisExtent(
  maxCrossAxisExtent: 180, crossAxisSpacing: 8, mainAxisSpacing: 8,
  childAspectRatio: 1,
)

Responsive crossAxisCount from MediaQuery

Compute the count once in build and feed it to the delegate — or skip the math entirely with MaxCrossAxisExtent above.

final w = MediaQuery.sizeOf(context).width;
final crossAxisCount = w > 1024 ? 5 : w > 768 ? 4 : w > 600 ? 3 : 2;

SliverGrid inside CustomScrollView

When the grid shares a scroll view with headers and other lists, extract it as a SliverGrid instead of nesting scrollables.

CustomScrollView(
  slivers: [
    const SliverToBoxAdapter(child: SectionHeader('Catalog')),
    SliverGrid(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3, childAspectRatio: 0.75),
      delegate: SliverChildBuilderDelegate(
          (ctx, i) => ProductTile(product: products[i]), childCount: products.length),
    ),
  ],
)

Gotchas and best practices

  • Avoid shrinkWrap: true on grids inside scrollables — it lays out every child; use SliverGrid instead.
  • GridView.count / GridView.extent are prototype shorthands; builder is the production default.
  • Raise cacheExtent past the default 250px for expensive cells (images, blur), and give stateful cells a key (ValueKey(item.id)) so their state follows the item through reorders.

A lazy GridView.builder with the right delegate and aspect ratio scales from a dozen thumbnails to a catalog without changing a line of layout code.