React NativeFlashListPerformanceLists

FlashList v2 vs FlatList: Which React Native List Should You Use in 2026?

FlashList v2 vs FlatList compared — how recycling works, why estimatedItemSize is gone, masonry layouts, chat lists with maintainVisibleContentPosition, migration steps, and performance tips for smooth React Native lists in Expo.

DeveloperMill4 min read

Every React Native app eventually hits the list problem: a feed, a product grid, or a chat that stutters, shows blank rows on fast scrolls, or eats memory. FlatList is the built-in answer. FlashList by Shopify is the popular high-performance alternative. FlashList v2, a ground-up rewrite for the New Architecture, makes the choice easier than ever.

How they differ under the hood

FlatList is a virtualized list. It renders a window of items around the viewport and unmounts and remounts components as you scroll. Each new row means new component instances, new native views, and more garbage collection.

FlashList recycles views. When a row scrolls off-screen, its native view is reused for the next row coming in, and only the data changes. It's the same idea as UICollectionView on iOS and RecyclerView on Android, which is why it holds 60 fps on long, image-heavy lists.

What changed in FlashList v2

  • No more size estimates. estimatedItemSize, estimatedListSize, and estimatedFirstItemOffset are gone. v2 measures items synchronously using the New Architecture and positions them precisely.
  • New Architecture only. v2 doesn't run on the old architecture. (Expo SDK 55+ is New Architecture by default.)
  • Masonry is a prop. MasonryFlashList was replaced by masonry on the regular FlashList.
  • No inverted. For chat UIs, use maintainVisibleContentPosition instead.
  • JS-only implementation, which makes it easier to debug and contribute to.

Install

npx expo install @shopify/flash-list

Basic usage

The API is intentionally close to FlatList. Most of the time, you just swap the import:

import { FlashList } from "@shopify/flash-list"

type Product = { id: string; title: string; price: number }

export function ProductList({ products }: { products: Product[] }) {
  return (
    <FlashList
      data={products}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ProductRow product={item} />}
    />
  )
}

No estimatedItemSize is needed. If you're migrating from v1, delete it.

Mixed item types: use getItemType

Recycling works best when a recycled view is reused for a similar item. If your list mixes headers, ads, and product rows, tell FlashList which is which:

<FlashList
  data={feed}
  keyExtractor={(item) => item.id}
  getItemType={(item) => item.type} // "header" | "product" | "ad"
  renderItem={({ item }) => {
    if (item.type === "header") return <SectionHeader title={item.title} />
    if (item.type === "ad") return <AdCard ad={item} />
    return <ProductRow product={item} />
  }}
/>

This is the single biggest performance setting for heterogeneous lists.

Masonry grids (Pinterest-style)

<FlashList
  data={photos}
  masonry
  numColumns={2}
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => (
    <Image source={item.uri} style={{ aspectRatio: item.ratio, borderRadius: 12 }} />
  )}
/>

Each item keeps its own height and is placed into the shortest column. Pair it with expo-image for caching and smooth image loading.

Chat lists without inverted

Chat UIs used to rely on inverted. In v2, keep the list in normal order and let FlashList hold the scroll position when new content arrives:

<FlashList
  data={messages}
  keyExtractor={(m) => m.id}
  renderItem={({ item }) => <MessageBubble message={item} />}
  maintainVisibleContentPosition={{ autoscrollToBottomThreshold: 0.2, startRenderingFromBottom: true }}
  onStartReached={loadOlderMessages}
/>

The recycling gotcha: local state

Because components are reused, useState inside a row can leak from one item to the next. For example, a "liked" heart might appear on the wrong row. Either keep per-item state in your data or store, or use FlashList's useRecyclingState, which resets when the item changes:

import { useRecyclingState } from "@shopify/flash-list"

function ProductRow({ product }: { product: Product }) {
  const [expanded, setExpanded] = useRecyclingState(false, [product.id])
  // ...
}

Migrating from FlatList (or FlashList v1)

  1. Make sure you're on the New Architecture (any Expo SDK 55+ app).
  2. Replace FlatList with FlashList, keeping data, renderItem, and keyExtractor.
  3. Delete estimatedItemSize and the other estimate props.
  4. Add getItemType for mixed lists.
  5. Replace MasonryFlashList with masonry, and inverted with maintainVisibleContentPosition.
  6. Audit useState in row components for recycling leaks.
  7. Measure in a release build. Dev mode isn't representative.

Performance tips for any list

  • Memoize row components (React.memo) and keep renderItem stable.
  • Don't create inline objects or closures for every row when you can avoid it.
  • Use expo-image with fixed dimensions or aspectRatio for images.
  • Keep rows shallow. Deep view trees cost more than item count.
  • Avoid animating layout props inside rows. Animate transform and opacity with Reanimated 4.

Verdict

  • Use FlashList v2 for feeds, product grids, chats, search results, and anything long or image-heavy.
  • Use FlatList for short, static lists where adding a dependency isn't worth it.
  • Use Expo UI lists for native-styled settings and forms. They recycle rows as of SDK 58, as covered in our Expo UI guide.

Building an e-commerce or social app? Start from one of our Expo templates instead of a blank screen, and apply the tips above as you add your own lists.

Frequently asked questions

Is FlashList better than FlatList?

For long or complex lists, yes. FlashList recycles item views instead of creating and destroying them as you scroll, which means fewer blank cells, lower memory use, and smoother scrolling — especially on low-end Android devices. For short, simple lists (a few dozen rows), FlatList is perfectly fine.

Does FlashList v2 still need estimatedItemSize?

No. FlashList v2 removed estimatedItemSize, estimatedListSize, and estimatedFirstItemOffset. It uses the New Architecture's synchronous layout measurement to size items precisely, so you can simply delete those props when migrating.

Does FlashList v2 work on the old architecture?

No. FlashList v2 is a ground-up rewrite that requires React Native's New Architecture. If your app is still on the old architecture, stay on FlashList v1 until you migrate.

How do I build a masonry grid with FlashList v2?

MasonryFlashList is gone in v2 — add the `masonry` prop to a regular FlashList along with `numColumns`. Items keep their own heights and are placed into the shortest column.

Build it faster with a template

Production-ready Expo React Native templates with the patterns from this post already wired up.

Browse templates