skip to content

FlashList Recycling

FlashList reuses mounted cells for new rows instead of mounting fresh ones, so local state can leak into the wrong row. Interviewers ask what v2 changed and when FlatList is still enough.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

5

What is FlashList in React Native, and how does its cell recycling differ from FlatList's virtualization?

level: juniorimportance: must knowfreq 52%

answer

  1. keep the cell, swap the item
  2. FlatList unmounts outside its window
  3. small draw distance, not viewports
  4. no key prop inside rows
  5. v2 needs the New Architecture

basics

~20 s

FlashList (@shopify/flash-list) is a FlatList-compatible list that recycles: a cell scrolled off screen stays mounted and is re-rendered with a different item. FlatList instead unmounts cells outside its render window and mounts new ones as you scroll.

solid answer

~50 s

Both lists render only part of a long `data` array, but they treat off-screen rows differently. `FlatList` (a `VirtualizedList`) keeps a render window measured in viewports - `windowSize` defaults to 21 - and rows that leave it are unmounted, so scrolling keeps mounting fresh components. `FlashList` keeps a pool of mounted cells and, when one scrolls out, re-renders it with a **different `item` prop** - no unmount, no mount - with a small buffer set by `drawDistance` in pixels. Re-rendering an existing cell is cheaper than building a new one, which means less JavaScript work and fewer blank areas on fast scrolls. The price: component state, refs and a `key` prop inside the row now survive or fight recycling. The API is almost FlatList's, so migrating is mostly renaming the component, removing `key` props inside rows and checking row `useState`. FlashList v2 requires the New Architecture.

go deeper

for a junior

Know that FlashList reuses mounted cells for new items while FlatList unmounts rows that leave its window, and that migrating is mostly a component rename.

for a middle

Explain why re-rendering a pooled cell is cheaper than mounting a new one, and what that means for useState, key props and memoized list props.

for a senior

Decide from release-build measurements whether a list needs FlashList, and make its rows recycle-safe as part of the migration rather than after the bug reports.

for a principal

Set a team rule for when a list may use FlashList and what a recycle-safe row looks like, so migrations do not reintroduce stale-state bugs screen by screen.

## Two ways to keep a long list cheap A React Native marketplace grid can hold thousands of listings, and rendering every one as native views would exhaust memory and block the JavaScript thread. Both built-in `FlatList` and the FlashList library (`@shopify/flash-list`) render only what is near the viewport. They differ in what happens to a row that scrolls away. **Virtualization in FlatList.** `FlatList` is built on `VirtualizedList`. It keeps a **render window** around the viewport - `windowSize` is measured in viewport lengths and defaults to 21 - and renders spacers for the regions outside it. A row that leaves the window is **unmounted**: its component state is discarded and its native views are released. A row that enters is **mounted** from scratch. **Recycling in FlashList.** FlashList keeps a pool of mounted cells. When a cell scrolls out of view it is not destroyed; the list **re-renders the same component with a different `item` prop** and moves it to its new position. The FlashList docs put it plainly: when an item gets out of the viewport, instead of being destroyed, the component is re-rendered with a different `item`. ## Side by side | | `FlatList` | FlashList v2 | |---|---|---| | Off-screen row | unmounted, later mounted fresh | kept mounted, re-rendered with a new item | | Buffer around the viewport | `windowSize` in viewports (default 21) | `drawDistance` in pixels (default 250 on iOS and Android) | | Tuning props | `windowSize`, `initialNumToRender`, `maxToRenderPerBatch`, `getItemLayout` | none of those; sizes are measured, not estimated | | Row `useState` | starts fresh for each row | carries over to the next item unless reset | | Architecture | core React Native | New Architecture only (v2) | Re-rendering an existing component tree with new props lets React update text, images and styles in place instead of creating every view again. That is less JavaScript work per scrolled row, less memory churn, and usually fewer **blank areas** - the empty gaps a list shows when rendering cannot keep up with a fast fling. ## What recycling asks of your row component Recycling changes the contract with the row component: - **State leaks between items.** A `useState` initialised from `item` keeps the previous item's value after recycling. Reset it, or better, derive it from data. - **No `key` prop inside rows.** A `key` that changes with the item makes React treat the tree as new and remount it, which throws the recycling benefit away. When a row maps over an array, the library's `useMappingHelper` supplies recycling-friendly keys. - **Keep renders cheap.** A recycled cell re-renders on every reuse, so heavy computation in the row costs on every scroll step. - **Memoize props passed to the list.** The v2 docs say this matters more than in v1, which skipped some updates in ways developers perceived as bugs. ## Switching a list over The FlashList docs' own migration steps boil down to: 1. Change the component name from `FlatList` to `FlashList`. 2. Remove explicit `key` props from the `renderItem` hierarchy. 3. Check row components that use `useState` and decide whether that state must reset for a new item. 4. Pass `getItemType` if the list mixes very different row kinds. 5. Provide a real `keyExtractor`; v2 recommends it to avoid glitches when layouts change while scrolling upward. 6. Measure in a release build - FlashList can look slower than FlatList in development mode because of its small render buffer. ## When FlatList is still enough - **Short lists.** A settings screen or a list of 30 items never scrolls enough for recycling to matter. - **Rows that are hard to reset.** Rows full of local state, timers or nested players may cost more to make recycle-safe than the gain is worth. - **An app not yet on the New Architecture.** FlashList v2 throws at import on the legacy architecture; since React Native 0.82 the New Architecture is the only one, so this matters only for older apps. - **FlatList-only props.** FlashList has no `getItemLayout` or `windowSize`; code built around them needs rework. The rule of thumb in interviews: reach for FlashList when a long or image-heavy list shows dropped frames or blank areas in a release build, and make the rows recycle-safe as part of the switch.

  • Why can FlashList look slower than FlatList during development?
    Because development mode makes every render much slower, and FlashList renders a deliberately small buffer around the viewport (`drawDistance`, 250 pixels by default on iOS and Android) where FlatList keeps many viewports rendered ahead. With slow dev-mode renders the small buffer runs out first. The FlashList docs say to profile only in a release build, where the comparison is meaningful.
  • Does recycling mean FlashList never unmounts a row?
    No. Cells are pooled per item type and reused where possible, but a cell can still be unmounted - for example when the recycle pool is capped with `maxItemsInRecyclePool` (setting it to 0 disables the pool so off-screen items unmount), or when a changing `key` inside the row forces React to remount it.

A restaurant that clears and relays a table for every new party is FlatList; FlashList keeps the table laid and only swaps the name card and the plates. It is faster, as long as nobody forgets to clear the last party's leftovers - the component state that recycling carries over.

saying these in an interview costs you the question

  • FlashList is FlatList with a larger default windowSize.
  • Recycling means row components keep their own state per item automatically.
  • Adding key={item.id} inside a FlashList row makes recycling more reliable.
  • FlashList v2 still needs estimatedItemSize to lay rows out.
  • FlashList helps every list, so even a 20-row settings screen should use it.
open as a page

In a React Native FlashList grid mixing listing cards and sponsored banners, what does getItemType do, and why does it matter?

level: middleimportance: should knowfreq 28%

basics

~20 s

FlashList's getItemType(item, index) labels each item with a type, and cells are recycled only between items of the same type. Without it, a sponsored-banner cell can be reused for a listing card, forcing React to rebuild its subtree.

open as a page

What changed in FlashList v2 for React Native compared with v1, and what do you remove or rename when migrating?

level: middleimportance: should knowfreq 35%

basics

~20 s

FlashList v2 runs only on the New Architecture, measures rows itself so estimatedItemSize and the other size estimates are gone, turns MasonryFlashList into a masonry prop, types refs as FlashListRef, and enables maintainVisibleContentPosition by default.

open as a page

In a React Native FlashList marketplace grid, recycled cells show the wrong favourite state after scrolling; why, and how do you fix it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

FlashList reuses a mounted cell for a new listing, and useState's initial value is read only at mount, so the previous listing's favourite flag survives. Derive the flag from data, or use useRecyclingState keyed on item.id.

open as a page

In a FlashList v2 marketplace grid, how do you make a featured listing span two columns, and when do you need masonry instead?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

In FlashList v2, overrideItemLayout sets layout.span for an item, so a featured listing can fill two columns of a numColumns grid. Use the masonry prop when cells have different heights and columns should grow independently instead of in aligned rows.

open as a page