skip to content

Virtualized Lists

FlatList and SectionList mount only a window of rows around the viewport, and FlashList recycles cells instead. Long scrolling feeds are the classic React Native performance interview question.

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

explore

questions

25

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 FlatList, what does keyExtractor do, and which key does the list use when you omit it?

level: juniorimportance: must knowfreq 72%

basics

~20 s

keyExtractor returns a unique, stable string for each FlatList item; the list uses it as the React key and to cache that cell's measurements. Without it, FlatList tries item.key, then item.id, then falls back to the index and warns about missing keys.

open as a page

How do you add pull-to-refresh to a React Native FlatList, and what do its refreshing and onRefresh props do?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Passing onRefresh makes a React Native FlatList add a standard RefreshControl; refreshing is the controlled boolean that shows the indicator. Set refreshing true when the reload starts and false when it ends, and replace the first page rather than appending.

open as a page

In a React Native SectionList, what shape must the sections prop have, and what do renderItem and renderSectionHeader receive?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A React Native SectionList takes sections: an array of objects, each with a required data array plus any fields you add, such as title. renderItem receives item, index within its section, section and separators; renderSectionHeader and renderSectionFooter receive the section.

open as a page

In a React Native FlatList, why can rows ignore a state change such as a selected contact, and what does extraData fix?

level: middleimportance: must knowfreq 58%

basics

~20 s

FlatList is a PureComponent, so it skips re-rendering when its props are shallowly equal. If renderItem depends on state outside data, such as a selected id, pass that state as extraData so the list re-renders and rows see the change.

open as a page

In a React Native FlatList feed, why can onEndReached fire twice for one page, and how do you guard the page load?

level: middleimportance: must knowfreq 58%

basics

~20 s

FlatList's onEndReached fires once per content length while the list sits near its end, so a mounting footer, settling row heights or scrolling out and back re-fires it. Guard the loader with a synchronous in-flight ref and a hasMore flag.

open as a page

In a React Native FlatList, what do windowSize, initialNumToRender and maxToRenderPerBatch control, and what are their defaults?

level: middleimportance: must knowfreq 62%

basics

~10 s

windowSize sets how many viewport lengths of rows stay mounted (default 21); initialNumToRender sets how many rows the first render mounts (default 10); maxToRenderPerBatch caps the new rows added per incremental batch (default 10).

open as a page

A React Native FlatList of 10,000 stock-ticker rows shows blank rows on fast flings; what causes the blanks, and which props would you tune, at what cost?

level: seniorimportance: must knowfreq 55%

basics

~20 s

The fling outruns the fill rate: rows beyond the render window are empty spacers until JavaScript renders the next batch. Lighter rows, getItemLayout, larger batches and a wider window help, costing JavaScript-thread time and memory.

open as a page

In a React Native FlatList, how do ItemSeparatorComponent, ListHeaderComponent, ListFooterComponent and ListEmptyComponent behave?

level: juniorimportance: should knowfreq 48%

basics

~20 s

In a React Native FlatList, ItemSeparatorComponent renders between items but not above the first or below the last; ListHeaderComponent and ListFooterComponent render before and after all items and scroll with them; ListEmptyComponent renders when data is empty.

open as a page

In a React Native FlatList, why should the row component returned by renderItem usually be wrapped in React.memo?

level: juniorimportance: should knowfreq 58%

basics

~20 s

When a FlatList re-renders, every mounted cell calls renderItem again, which can mean hundreds of rows. Wrapping the row in React.memo lets rows with unchanged props skip rendering, so one updated item re-renders one row.

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 FlatList, how does numColumns lay out a grid, and why does changing it at runtime throw an error?

level: middleimportance: should knowfreq 38%

basics

~20 s

With numColumns above 1, a React Native FlatList groups items into row views of that many items, filling rows left to right. It cannot be combined with horizontal, and changing numColumns after mount throws; change the list's key to remount it instead.

open as a page

In a React Native FlatList with inverted set, as in a live comment thread, how does inversion work and which callback loads older comments?

level: middleimportance: should knowfreq 38%

basics

~20 s

An inverted React Native FlatList flips its scroll container with a -1 scale transform and flips every cell back, so data[0] renders at the bottom. The logical end is the visual top, so onEndReached loads older comments.

open as a page

Why do React Native SectionList section headers stick on iOS but scroll away on Android, and how do you make them consistent?

level: middleimportance: should knowfreq 30%

basics

~20 s

stickySectionHeadersEnabled defaults to true on iOS and false on Android, following each platform's convention. Set it explicitly on the React Native SectionList to get the same behaviour on both, and give headers an opaque background.

open as a page

In a React Native FlatList, what does getItemLayout do, when can you supply it, and what goes wrong if its offsets are wrong?

level: middleimportance: should knowfreq 52%

basics

~20 s

getItemLayout returns each row's length and offset from the data, so FlatList can skip measuring rendered cells and size spacers exactly. It suits rows whose sizes are known in advance; wrong offsets cause jumping content and misplaced scrollToIndex targets.

open as a page

In a React Native FlatList, what does removeClippedSubviews do, what is its default per platform, and why can it hide content?

level: middleimportance: should knowfreq 32%

basics

~20 s

removeClippedSubviews detaches native views outside the viewport from the native hierarchy to cut drawing work. FlatList defaults it to true on Android and false on iOS; transforms or absolute positioning can make visible content disappear.

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

Why can scrollToIndex on a React Native FlatList throw or fail for a far-away row, and how do you make it reliable?

level: seniorimportance: should knowfreq 35%

basics

~20 s

FlatList only knows the offsets of rows it has measured. scrollToIndex to an unmeasured row throws unless getItemLayout or onScrollToIndexFailed is set; the failure callback lets you scroll near the row and retry once it renders.

open as a page

In a React Native FlatList news feed, a reader pulls to refresh while a page-3 request is still in flight; what goes wrong, and how do you guard it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

The late page-3 response lands after the refresh has reset the feed, so stale stories are appended to a fresh page 1 and the page cursor skips ahead. Tag requests with a generation counter, drop mismatches, and block paging while refreshing.

open as a page

When is flattening grouped data into a FlatList with header rows simpler than a React Native SectionList, and what must you handle yourself?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Flatten grouped data into one array of header and item rows when the target list has no sections API, such as FlashList, or you want one index space. You then branch in renderItem, keep keys distinct and compute stickyHeaderIndices yourself.

open as a page

How does scrollToLocation address a row in a React Native SectionList, and why can it land on the header or under a sticky header?

level: seniorimportance: should knowfreq 25%

basics

~20 s

scrollToLocation({sectionIndex, itemIndex}) converts the pair into a flattened index where every section also has a header and footer slot, so itemIndex 0 lands on the section header. With sticky headers it adds the header height to viewOffset for itemIndex above 0.

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

In a React Native SectionList, how does SectionSeparatorComponent differ from ItemSeparatorComponent, and which props do separators receive?

level: middleimportance: nice to knowfreq 15%

basics

~20 s

In a React Native SectionList, ItemSeparatorComponent renders between items within a section only, while SectionSeparatorComponent renders before a section's first item and after its last. Both receive highlighted, section and leading/trailing item and section props when passed as components.

open as a page

In a React Native FlatList news feed, new stories are prepended while a reader is mid-list; why does content jump, and how does maintainVisibleContentPosition help?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

A scroll view keeps its offset from the top, so rows inserted above push the story being read downward. maintainVisibleContentPosition makes React Native shift the offset so the first visible row at or after minIndexForVisible stays put.

open as a page