FlashList vs FlatList vs LegendList: React Native Lists 2026
FlashList vs FlatList vs LegendList: React Native Lists 2026
TL;DR
Rendering long lists in React Native is famously challenging — FlatList is the built-in solution but struggles with scroll jank and blank flashes for large datasets. FlatList (built into React Native) works for lists under a few hundred items but degrades significantly beyond that. FlashList (Shopify) is the production-tested replacement — uses a recycler pattern that reuses rendered components instead of destroying and recreating them, achieving 5-10x better performance for large datasets. LegendList is the newest entry — built on React Native's Fabric architecture and Reanimated, claiming to fix blank flashes and providing even smoother scrolling for very large lists. For most apps (< 500 items): FlatList. For production apps with large datasets (500+ items): FlashList. For apps hitting FlashList's limits or needing the smoothest possible experience: LegendList.
Key Takeaways
- FlatList blanks at high scroll speed — unmounted items render as blank white space
- FlashList reuses DOM components — no creation/destruction overhead; 5-10x faster than FlatList
- LegendList is built on Fabric/JSI — new architecture only, but eliminates blank flashes
- FlashList requires
estimatedItemSize— must set an estimated item height/width - FlatList is zero setup — built into React Native, works for most use cases
- FlashList GitHub stars: 4k — Shopify uses it in their production apps
- All three support horizontal lists —
horizontalprop works across all
Why FlatList Struggles at Scale
React Native's FlatList uses a windowing approach:
List with 10,000 items:
- Renders ~20 items in the visible viewport
- Destroys components as they scroll off-screen
- Creates new components as they scroll into view
Problem: Creation/destruction is expensive
- JS creates new component instances
- Bridge sends layout data to native
- At high scroll speed: blank flash while new items render
FlashList's recycler approach:
FlashList recycler:
- Keeps ~30 component instances in memory
- When item scrolls off: recycle it for the next incoming item
- Update props instead of recreating component
- No blank flashes — always showing content
FlatList: The Built-In Standard
FlatList is React Native's built-in virtualized list. Zero setup, well-documented, and sufficient for most use cases.
Basic Usage
import { FlatList, View, Text, StyleSheet } from "react-native";
interface Post {
id: string;
title: string;
author: string;
publishedAt: string;
}
function PostItem({ item }: { item: Post }) {
return (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.author}>{item.author}</Text>
<Text style={styles.date}>{item.publishedAt}</Text>
</View>
);
}
function PostsList({ posts }: { posts: Post[] }) {
return (
<FlatList
data={posts}
renderItem={({ item }) => <PostItem item={item} />}
keyExtractor={(item) => item.id}
// Performance optimizations
removeClippedSubviews={true} // Unmount off-screen views
maxToRenderPerBatch={10} // Batch render 10 at a time
windowSize={10} // Render 10 * viewport height
initialNumToRender={15} // First render: 15 items
getItemLayout={(data, index) => ({ // Fixed height optimization
length: 80,
offset: 80 * index,
index,
})}
/>
);
}
FlatList Optimization Tips
// 1. Memoize renderItem to prevent unnecessary re-renders
const renderItem = useCallback(
({ item }: { item: Post }) => <PostItem item={item} />,
[]
);
// 2. Memoize PostItem component
const PostItem = memo(({ item }: { item: Post }) => {
return (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
});
// 3. Use getItemLayout for fixed-height items (much faster)
const getItemLayout = useCallback(
(data: Post[] | null | undefined, index: number) => ({
length: 80, // Fixed item height
offset: 80 * index,
index,
}),
[]
);
// 4. Use keyExtractor returning stable IDs
const keyExtractor = useCallback((item: Post) => item.id, []);
function OptimizedList({ posts }: { posts: Post[] }) {
return (
<FlatList
data={posts}
renderItem={renderItem}
keyExtractor={keyExtractor}
getItemLayout={getItemLayout}
removeClippedSubviews={true}
maxToRenderPerBatch={20}
windowSize={21}
/>
);
}
Pull-to-Refresh and Infinite Scroll
function InfinitePostsList() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [page, setPage] = useState(1);
const loadMore = async () => {
if (loading) return;
setLoading(true);
const newPosts = await fetchPosts(page + 1);
setPosts((prev) => [...prev, ...newPosts]);
setPage((p) => p + 1);
setLoading(false);
};
const refresh = async () => {
setRefreshing(true);
const freshPosts = await fetchPosts(1);
setPosts(freshPosts);
setPage(1);
setRefreshing(false);
};
return (
<FlatList
data={posts}
renderItem={({ item }) => <PostItem item={item} />}
keyExtractor={(item) => item.id}
onEndReached={loadMore}
onEndReachedThreshold={0.3}
refreshing={refreshing}
onRefresh={refresh}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
/>
);
}
FlashList: Production-Grade Performance
FlashList from Shopify uses a recycler algorithm that achieves near-native scroll performance for large lists.
Installation
npx expo install @shopify/flash-list
Basic Usage (Drop-in for FlatList)
import { FlashList } from "@shopify/flash-list";
function PostsList({ posts }: { posts: Post[] }) {
return (
<FlashList
data={posts}
renderItem={({ item }) => <PostItem item={item} />}
keyExtractor={(item) => item.id}
estimatedItemSize={80} // Required — FlashList uses this for layout estimation
/>
);
}
FlashList with Variable Height Items
// FlashList handles variable-height items well
// Just provide estimatedItemSize as average height
function MessagesList({ messages }: { messages: Message[] }) {
return (
<FlashList
data={messages}
renderItem={({ item }) => (
<MessageItem
text={item.text}
userId={item.userId}
timestamp={item.timestamp}
/>
)}
estimatedItemSize={60} // Average message height
keyExtractor={(item) => item.id}
inverted // Show newest messages at bottom (chat-style)
onEndReached={loadOlderMessages}
onEndReachedThreshold={0.3}
/>
);
}
FlashList Performance Tuning
// Advanced FlashList configuration for maximum performance
function OptimizedFlashList({ data }: { data: Item[] }) {
return (
<FlashList
data={data}
renderItem={({ item }) => <ItemComponent item={item} />}
estimatedItemSize={100}
// Override recycling type per item (for mixed layouts)
getItemType={(item) => {
if (item.type === "header") return "header";
if (item.type === "ad") return "ad";
return "default";
}}
// Pre-render invisible items for smoother scrolling
drawDistance={300} // Pixels above/below viewport to pre-render
// Override estimated list size for better initial scroll
estimatedListSize={{ height: 2000, width: 400 }}
// Diagnostic: shows which items are recycled vs new
// (development only)
// disableAutoLayout={false}
/>
);
}
Migrating from FlatList to FlashList
// Before (FlatList)
import { FlatList } from "react-native";
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
/>
// After (FlashList) — add estimatedItemSize, remove FlatList-specific props
import { FlashList } from "@shopify/flash-list";
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
estimatedItemSize={80} // Add this
// Remove: removeClippedSubviews, maxToRenderPerBatch — FlashList handles these internally
/>
LegendList: New Architecture Performance
LegendList is built for React Native's new architecture (Fabric + JSI). It aims to eliminate blank flashes entirely through synchronized rendering.
Installation
npm install @legendapp/list
# Requires React Native New Architecture (Fabric)
Basic Usage
import { LegendList } from "@legendapp/list";
function PostsList({ posts }: { posts: Post[] }) {
return (
<LegendList
data={posts}
renderItem={({ item }) => <PostItem item={item} />}
keyExtractor={(item) => item.id}
estimatedItemSize={80}
// LegendList-specific: recycles with Reanimated for buttery smooth scrolling
recycleItems
/>
);
}
LegendList with Dynamic Heights
// LegendList handles dynamic heights better than FlashList
// No need to specify item type for mixed layouts
function DynamicList({ items }: { items: FeedItem[] }) {
return (
<LegendList
data={items}
renderItem={({ item }) => {
if (item.type === "image") return <ImageCard item={item} />;
if (item.type === "text") return <TextCard item={item} />;
return <DefaultCard item={item} />;
}}
estimatedItemSize={120}
recycleItems
maintainScrollAtEnd // Keeps scroll position when new items added at bottom
/>
);
}
Performance Benchmark
Based on community benchmarks (scrolling 10,000 item list at maximum speed):
| Metric | FlatList | FlashList | LegendList |
|---|---|---|---|
| Blank flashes | Frequent | Rare | None* |
| Memory usage | High | Low | Low |
| Initial render time | Medium | Fast | Fast |
| 60fps scroll | Often drops | Mostly yes | Yes (Fabric) |
| Large dataset (10k) | Slow | ✅ Fast | ✅ Fast |
| Setup effort | Zero | estimatedItemSize | Fabric required |
| Mixed item types | Manual | getItemType | Auto |
*LegendList requires New Architecture (Fabric)
Feature Comparison
| Feature | FlatList | FlashList | LegendList |
|---|---|---|---|
| Built-in | ✅ | ❌ | ❌ |
| New Architecture required | ❌ | ❌ | ✅ |
| Recycler pattern | ❌ | ✅ | ✅ |
| Variable heights | ✅ | ✅ | ✅ |
| estimatedItemSize | ❌ | Required | Required |
| Horizontal | ✅ | ✅ | ✅ |
| SectionList equiv | SectionList | ✅ sections | In progress |
| Pull-to-refresh | ✅ | ✅ | ✅ |
| GitHub stars | Built-in | 4.4k | 1.5k |
| Production usage | ✅ Universal | ✅ Shopify | Growing |
When to Use Each
Choose FlatList if:
- Your list has under 200-300 items and performance is acceptable
- You need zero additional dependencies
- You're working with SectionList (grouped items) — use React Native's SectionList
- Compatibility with older React Native versions matters
Choose FlashList if:
- Your list has 500+ items or performance is visibly poor with FlatList
- You're on the old React Native architecture (not yet on Fabric)
- You're building a production app (Shopify uses this at scale)
- You need a reliable drop-in FlatList replacement with minimal API changes
Choose LegendList if:
- You're already on React Native's New Architecture (Fabric/Nitro)
- Blank flashes are unacceptable (media feeds, photo galleries)
- You want the smoothest possible scrolling for very large datasets
- Dynamic height items cause issues with FlashList
Methodology
Data sourced from official FlashList documentation (shopify.github.io/flash-list), LegendList documentation (legendapp.com), community benchmarks published on Twitter/X by React Native engineers, GitHub star counts as of February 2026, and performance reports from the React Native Discord. FlatList behavior documented from the official React Native docs and community reports on GitHub issues.
Related: React Native Reanimated vs Moti vs Skia for animation in your list items, or Expo Router vs React Navigation vs Solito for navigation between list screens.