FlashList vs FlatList vs LegendList 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
Ecosystem and Community Health
FlashList is the most mature of the three non-built-in options. Shopify built it to replace FlatList in their own apps — including the Shopify app, which handles merchant catalogs with thousands of product variants — and open-sourced it with 4.4k GitHub stars. The library is actively maintained, has a dedicated issues tracker, and integrates seamlessly with Expo. Because Shopify uses it at real production scale, regressions tend to be caught quickly and fixed promptly.
FlatList will never go away because it's part of React Native core. The React Native team at Meta and Microsoft continues to improve it, but the fundamental architecture — create and destroy component instances as they scroll — cannot be changed without breaking backward compatibility. FlatList is what you get by default, and it works well enough that millions of production apps rely on it for lists that don't push performance limits.
LegendList comes from the makers of Legend State, a high-performance React state management library that uses Signals-like reactivity. Jay Meistrich, the creator, built LegendList specifically to eliminate the remaining scroll performance issues that FlashList doesn't fully address on the new React Native architecture. The library is newer — around 1.5k GitHub stars — but growing rapidly as Fabric adoption increases. The constraint is hard: LegendList requires New Architecture. If any of your dependencies still require the old bridge, LegendList is not yet an option.
Real-World Adoption and Production Use Cases
Shopify's primary app uses FlashList for the product catalog, order history, and customer lists — contexts where merchants may have thousands of items. The 5-10x performance improvement over FlatList was the original motivation for building FlashList in the first place. After open-sourcing in 2022, it was adopted rapidly across the React Native ecosystem. For the navigation patterns that typically host these list views, see Expo Router vs React Navigation vs Solito.
Discord's mobile app, which uses React Native for significant portions of its interface, uses FlashList-style recycling for message lists. Chat applications are among the most demanding list scenarios because messages have highly variable heights (short one-liners vs long code blocks), content is constantly being added at the bottom, and the list must remain scrolled to the latest message. FlashList handles this case significantly better than FlatList. For styling these list items consistently across platforms, see NativeWind vs Tamagui vs TWRNC React Native styling 2026.
For apps targeting the absolute bleeding edge of React Native performance — native module developers, startups building on Fabric from the start — LegendList is gaining traction. Early benchmarks published by the React Native community show LegendList achieving sustained 60fps even on mid-range Android devices when scrolling 10,000-item lists at maximum speed, something that FlashList occasionally drops frames on when items have complex nested components.
FlatList remains the correct choice for a large category of real-world apps. Any list that comfortably renders under 300-400 items will work fine with FlatList, and the zero-dependency, zero-configuration story is genuinely valuable for apps that prioritize simplicity. Feature announcement lists, settings screens, notification feeds with daily batches — these don't need recycling.
Developer Experience Deep Dive
The FlashList migration from FlatList is nearly frictionless. The API is intentionally compatible — replace the import, add estimatedItemSize, remove a few FlatList-only props, and you're done. The estimatedItemSize prop is the one thing developers get wrong: it should be a realistic average item height in pixels, not an extreme. If your items average 120px but you provide estimatedItemSize={40}, FlashList will recalculate layouts frequently, reducing the performance benefit. The FlashList website includes a warning in the output when you're running development mode if the estimate is significantly off.
The getItemType prop is the other FlashList-specific concept worth understanding. If your list contains multiple item types (headers, regular items, ads, loading skeletons), getItemType tells FlashList which pool to recycle from. Without it, FlashList might try to recycle a header component into a regular item slot, causing visual glitches. The TypeScript types for this API are good — TypeScript will catch most mistakes at compile time.
LegendList's API adds a few new concepts: recycleItems is a boolean that enables the core recycling behavior, and maintainScrollAtEnd keeps the viewport pinned to the newest item when new data is appended at the bottom — essential for chat applications. The Fabric requirement means you need to be already running on New Architecture, which requires newArchEnabled: true in your gradle.properties and Podfile. If you're using Expo, this is a single toggle in app.json.
TypeScript support across all three is solid. FlatList ships with React Native's own type definitions. FlashList provides comprehensive TypeScript types including the generic item type parameter FlashList<T>. LegendList similarly provides full generics support.
Performance Benchmarks
Based on community-published benchmarks scrolling a 10,000-item list on a mid-range Android device (Pixel 5), the frame rate differences are substantial:
FlatList, unoptimized: drops to 20-30fps during fast scrolling, with visible blank flashes during rapid flings. Even with full optimization (memoization, getItemLayout, removeClippedSubviews), FlatList tops out around 40-50fps during aggressive scrolling.
FlashList on the same device maintains 58-60fps for most scrolling patterns. Blank flashes are rare — visible primarily during very fast flings on lists with complex images that need network fetching. Memory usage stays stable at around 150MB for 10,000 items compared to FlatList's 300-400MB as it accumulates destroyed component instances.
LegendList on the New Architecture achieves a genuinely different baseline: 60fps is the floor, not the ceiling. The JSI bridge eliminates the serialization overhead between JavaScript and the native layer, and Fabric's synchronous rendering means layout calculations happen in the same frame as interaction. For content-heavy feeds where each item contains images, text, and interactive elements, LegendList is measurably better.
Getting Started and Migration Guide
To migrate an existing FlatList to FlashList: install @shopify/flash-list via Expo or npm, swap the import, add estimatedItemSize with a realistic value, and remove removeClippedSubviews, maxToRenderPerBatch, and windowSize props — FlashList manages these internally. Run your app and check the yellow warning in development mode if the estimated size is too far off.
To try LegendList: first confirm you're on New Architecture (check for newArchEnabled: true in your build config), then npm install @legendapp/list, swap the import, add estimatedItemSize, and add recycleItems. Test your existing item components carefully after enabling recycling — stateful components that don't reset on prop changes will show stale content when recycled into a new item slot. Use useCallback and proper key management.
Final Verdict 2026
Use FlatList for lists under 200-300 items where performance is acceptable in testing. The zero-setup story is genuinely valuable for early-stage apps, and FlatList handles SectionList use cases that FlashList doesn't yet match.
Use FlashList for any list that shows performance problems — blank flashes, scroll jank, high memory usage. The migration is 15 minutes for a typical FlatList, and the performance improvement is immediate and dramatic. This should be the default choice for any production React Native app with meaningful list content.
Use LegendList if you're building on New Architecture and need the smoothest possible scrolling. If you're building a high-performance content feed, chat application, or media browser where list quality is a core product differentiator, LegendList represents the current ceiling for React Native list performance.
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: Best Real-Time Libraries 2026, Best Node.js Background Job Libraries 2026, Best JavaScript Testing Frameworks 2026