FlatList vs FlashList vs LegendList: Lists That Don't Stutter
FlatList vs FlashList v2 vs LegendList in 2026: how each renders long lists, when to use which, and the row-level fixes that keep lists smooth.
Full-Stack, Mobile & AI Engineer
Lists are where React Native apps most often feel slow. Feeds, chats, catalogs and search results are the screens people scroll most, and every blank flash, dropped frame or jump when an image loads is noticed. Most of the time, React Native FlatList performance problems (and list problems generally) have an unexotic fix: the right list component plus lighter rows.
In 2026 there are three main options for long lists: React Native's built-in FlatList, Shopify's FlashList (now version 2, rebuilt for the New Architecture) and LegendList. This guide compares them, explains when each wins, and lists the row-level fixes that matter regardless of which you choose.
Key takeaways
ScrollViewwith.map()is only for short lists; it renders every item up front.- FlatList virtualises: it renders a window of items around the viewport, and it's fine for moderate lists with simple rows.
- FlashList recycles views instead of creating new ones, which is faster for long, complex lists. Version 2 requires the New Architecture and no longer needs size estimates.
- LegendList is a fast JavaScript-based list with features that suit chats and dynamic content, such as keeping scroll at the end.
- Row weight matters more than the component: light rows, sized images and stable keys fix most scrolling problems.
The four options
ScrollView + map | FlatList | FlashList v2 | LegendList | |
|---|---|---|---|---|
| How it works | Renders everything | Virtualised window of items | Recycles cells as you scroll | Virtualised, with optional recycling |
| Built in | Yes | Yes | No (Shopify) | No (Legend) |
| Long lists | Poor | Good with tuning | Excellent | Excellent |
| Size estimates needed | No | Optional getItemLayout | No (v2 measures automatically) | No |
| Architecture | Any | Any | New Architecture only | Any |
| Good for | A handful of items | Moderate lists, simple rows | Large feeds, catalogs, complex rows | Chats, dynamic heights, scroll-to-end |
ScrollView with map() vs FlatList: the common question
items.map() inside a ScrollView creates every row immediately. With 20 items that's fine. With 2,000 it's slow to open and heavy on memory. FlatList renders only what's near the screen, adding and removing rows as you scroll. If a list can grow beyond a screen or two, use a list component.
FlatList: fine, with tuning
FlatList is the built-in choice and works well for many apps. If it struggles:
- Provide
getItemLayoutwhen rows have a fixed height, so it can skip measuring. - Tune
initialNumToRender,maxToRenderPerBatchandwindowSizeto balance blank areas against work. - Memoise rows (
React.memo) and pass stable callbacks.
When tuning stops helping, switch components rather than adding more tuning.
FlashList v2: the default for big lists
FlashList reuses ("recycles") the native views of rows that scroll off screen for new rows coming in, which avoids creating and destroying views constantly. Version 2 was a ground-up rewrite for the New Architecture:
- No
estimatedItemSizeneeded; sizes are handled automatically. maintainVisibleContentPositionis on by default, which helps lists that load content above the viewport.- Masonry layouts are a prop rather than a separate component.
For heterogeneous rows (posts, ads and headers in one feed), give FlashList a getItemType so it recycles views between rows of the same type.
LegendList: strong for chats and dynamic content
LegendList is a high-performance list written in JavaScript, with features aimed at the hard cases: lists that should stay pinned to the bottom (chats), items whose heights change after loading, and smooth scroll-to-index. It's a good fit for messaging screens and other content that grows at the end.
Row-level fixes that matter more than the component
- Keep rows light. Fewer nested views, no heavy computation during render, no inline formatting of large data.
- Memoise rows and avoid passing new objects or functions as props on every render.
- Size images correctly and use a caching image component, with placeholders so rows don't jump.
- Use stable keys from your data, never array indexes for lists that change.
- Paginate. Load 20 to 50 items at a time and fetch more near the end.
- Avoid expensive effects in rows, such as subscriptions per row or layout measurements on every render.
From the field: Feeds and chat are the screens where list performance is most visible. I've scaled real-time chat and collaboration to 100K+ concurrent users with Socket.IO and Redis, and built Spoxio's athlete feeds with video highlights. In both kinds of screen, the biggest gains come from lighter rows and correctly sized media, with the list component as the multiplier.
Chat and other bottom-anchored lists
Chat screens are a special case: new content arrives at the bottom, older messages load at the top, and the view must stay pinned to the latest message without jumping. The traditional trick is an inverted list, which flips the list so index 0 sits at the bottom. It works, but it complicates accessibility and some gestures.
Modern options handle this more directly. LegendList can keep the scroll position at the end as items are added and align short lists to the bottom, and FlashList v2's default maintainVisibleContentPosition keeps the visible messages steady when older ones are inserted above. Whichever you choose, test with long messages, images that load late, the keyboard opening and closing, and a slow network, because chat screens hit every edge case at once.
How to measure list performance
- Scroll the real list on a mid-range Android phone in a release build, with realistic data (long text, slow images, many items).
- Watch the performance monitor for JavaScript frame drops while scrolling.
- Look for blank areas during fast scrolls, a sign that rows render too slowly.
- Profile row renders with the React profiler to find components that re-render unnecessarily.
FAQ
Is FlashList better than FlatList?
For long lists with complex rows, usually yes, because FlashList recycles views instead of creating new ones. Version 2 also removes the need for size estimates. For short or simple lists, FlatList is perfectly adequate and has no extra dependency.
Does FlashList v2 work on the old architecture?
No. FlashList v2 is built for the New Architecture only. Apps still on the legacy architecture need FlashList v1, and should plan a New Architecture migration to use current versions.
What is LegendList?
LegendList is a high-performance list component for React Native, designed as an alternative to FlatList and FlashList. It handles dynamic item heights and scroll-to-end behaviour well, which makes it popular for chat screens and growing content.
Why does my React Native list show blank spaces when scrolling fast?
Rows are rendering more slowly than you're scrolling. Make rows lighter, memoise them, size images properly, and consider FlashList or LegendList. For FlatList, increasing windowSize reduces blanks at the cost of more memory.
Next steps
If a key list in your app stutters, I can profile it on real devices and fix it, usually with lighter rows and the right list component. Get in touch, or see how I work as a React Native developer.
Sources
- FlatList, React Native docs
- Optimizing FlatList configuration, React Native docs
- FlashList v2: a ground-up rewrite for the New Architecture, Shopify Engineering
- FlashList v2 migration guide, Shopify
- LegendList, GitHub
Related
60fps Animations with Reanimated: Patterns That Don't Drop Frames
React Native animations at 60fps with Reanimated 4: CSS transitions, gesture-driven motion, scroll effects, layout animations and real-time data visuals.
Real-Time Charts and Heat Maps at 60fps with Skia
Draw real-time charts and heat maps at 60fps in React Native with Skia and Reanimated: buffering sensor data, Victory Native, pictures and performance.
How We Cut React Native Startup Time from 3.8s to 1.5s
The levers that cut React Native startup time by about 60%: measuring cold start properly, Hermes, image optimisation and cache-first fetching.
