Why FlatList Beats ScrollView for Long Lists in React Native
ScrollView with .map() mounts every item instantly — killing performance on long lists. FlatList virtualizes, recycles, and stays fast. Here's the practical prop configuration that makes it work, a 5,000-row before/after example, and the trade-offs you'll hit with dynamic heights or FlashList.
24 Apr 2026, 06:37 UTC

The Problem: ScrollView Mounts Everything
You have a feed with 5,000 messages. The intuitive React approach is to .map() over the array inside a ScrollView. On iOS it might feel fine. On a low‑end Android device the app freezes for seconds, memory spikes, and scrolling drops frames. That's because ScrollView renders every child immediately — no virtualization, no recycling.
How FlatList Changes the Equation
FlatList is React Native's core virtualized list. It mounts only the rows near the viewport and recycles or unmounts them as the user scrolls. Initial render time and memory stay roughly constant whether you have 100 items or 10,000. The component handles the bookkeeping: measuring rows, calculating which indices are visible, and batching updates so the JS thread isn't overwhelmed.
Props That Actually Matter
Default values shift across React Native releases, so check the docs for your version. The props that move the needle in practice:
- initialNumToRender — how many rows mount on first paint. Set it to cover one screen plus a small buffer (e.g., 10–12 for typical feed rows).
- windowSize — rendered viewport buffer measured in screen heights. A value of 2–3 keeps a couple screens above/below ready without bloating memory.
- getItemLayout — when row heights are fixed, this lets FlatList skip per‑row measurement and enables instant
scrollToIndex. The function receives(data, index)and returns{length, offset, index}. - keyExtractor — must return stable, unique keys. Using
indexbreaks row state and triggers unnecessary re‑renders when items are inserted or removed.
Also memoize renderItem and the row component (React.memo, stable callbacks). Recreating renderItem on every parent render forces all visible rows to re‑render, nullifying virtualization.
Worked Example: 5,000‑Row Feed
Before — ScrollView + map
// ❌ Slow mount, high memory, janky scroll
function Feed({ messages }) {
return (
{messages.map((msg, i) => (
))}
);
}After — FlatList with tuned props
// ✅ Virtualized, stable keys, fixed‑height layout
const ROW_HEIGHT = 72;
function Feed({ messages }) {
const renderItem = useCallback(({ item }) => (
), []);
const getItemLayout = useCallback((_, index) => ({
length: ROW_HEIGHT,
offset: ROW_HEIGHT * index,
index,
}), []);
return (
item.id}
initialNumToRender={12}
windowSize={3}
getItemLayout={getItemLayout}
removeClippedSubviews={true}
listKey={"feed"}
/>
);
}
const MessageRow = React.memo(({ message }) => (
{message.text}
));Key differences: stable item.id keys, memoized row, fixed height enabling getItemLayout, and removeClippedSubviews (Android) to unmount off‑screen views from the native hierarchy.
Trade‑offs and When to Consider FlashList
getItemLayout only works when row heights are truly fixed. Variable‑length text, images with unknown dimensions, or dynamic layouts break the offset math and cause scroll jumps. If you can't guarantee fixed heights, omit getItemLayout and let FlatList measure — it's slower but correct.
Shopify's FlashList (v2+) uses a different recycling strategy and often outperforms FlatList on very large lists. Its API is FlatList‑like but not a drop‑in: prop names differ (estimatedItemSize instead of getItemLayout), and major versions have changed internals. Evaluate it against your current React Native version before adopting.
SectionList handles grouped data (e.g., dated sections) with the same virtualization model. Use it when you need sticky section headers.
How to Verify It Works
- Build a minimal demo rendering 1,000–5,000 rows both ways (ScrollView + map vs FlatList).
- Open the React Native DevTools profiler or the Performance Monitor from the dev menu.
- Compare mount time, JS heap, and frame drops during fast scroll.
- Test on a low‑end Android device or emulator — list jank rarely reproduces on modern iOS hardware.
If the FlatList version shows constant memory and smooth 60 fps scroll while the ScrollView version spikes and stalls, the engineering decision is validated. No benchmark numbers needed — just the observable difference on real hardware.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.