RN: FlashList for large lists, FlatList otherwise, ScrollView only for <20 static items. Web: TanStack Virtual (react-virtual) or react-window — both current (3.14.13 / 2.3.3, verified vs npm 2026-09-24); pick on API taste.
huge web tables (100k+ rows) → HighTable
one list engine across RN + web → Legend List
new large RN list surface where maintainer continuity outweighs library maturity → Legend List (actively released; FlashList is on critical-fixes-only while Shopify seeks a steward)
text-heavy RN feeds where hundreds of flat labels mount at once → react-native-plain-text for those labels (renders straight to UILabel/TextView; one string, one style, New Architecture only, 0.9.x pre-1.0; its unified `Text` selector auto-falls-back to RN `<Text>` for nesting/mixed styles/press/selection, so you no longer have to audit each element by hand)
exact thresholds / perf → react-native-best-practices (owns the rules)
Options & tradeoffs
the field considered — and why each one isn’t the default here
option
tradeoff
evidence
ScrollView/.map
fine <20 static items only
FlatList
RN baseline virtualization
FlashList
recycling; large RN lists (v2 = New Architecture ONLY; old arch → pin v1.x). 2.3.2 (2026-06-10, verified vs npm 2026-09-24, not deprecated); Shopify is leaving React Native and only fixes compatibility-breaking issues while it seeks a long-term steward. See note: FLASHLIST STEWARDSHIP
3.0M/wk · ships in 7/34
react-window / react-virtual / HighTable
web virtualization; HighTable handles billion-row tables (viewport-only DOM + scrollbar downscaling)
8.0M/wk · ships in 5/34
Legend List
one virtualization engine for RN + web (v3 stable; 3.4.0, 2026-09-21, verified vs npm 2026-09-24); SectionList + chat/anchoring APIs; signal-driven container pool → very small re-renders
v2 is a ground-up New-Architecture rewrite (recycling, progressive rendering, pre-paint correction) and is New-Arch-ONLY — on the old architecture stay pinned to v1.x until the RN ladder is done
v9 went stable 2026-08-04 and moves reactivity underneath the Table APIs — per-feature atoms behind a TableReactivityBindings contract, so grid re-renders track the state a cell actually uses instead of whole-table state; callers keep passing ordinary values and calling the same Table methods, so this is a version-line move rather than a rewrite
A list's cost scales with the nodes it MOUNTS, not the length of its data, so every tool here renders only what is on or near the screen — by windowing (mount the viewport, unmount the rest: FlatList, react-window, react-virtual) or by recycling (re-bind a fixed pool of views: FlashList). On React Native, FlatList's windowing is the baseline and FlashList's recycling wins at scale, where mount/unmount churn dominates; ScrollView mounts everything and is correct only for a few static rows. On the web the same logic applies to DOM nodes. Legend List is the one engine that covers both platforms. Exact thresholds are owned by react-native-best-practices (RN-RULE-USE-FLASHLIST).
FLASHLIST STEWARDSHIP (2026-09-21, verified vs Shopify's announcement + npm; npm re-checked 2026-09-24): Shopify is migrating its apps off React Native back to Swift/Kotlin. For FlashList — which the post puts at ~2M downloads/week and calls "the default way to render high-performance lists in React Native" — Shopify commits only to FIXING CRITICAL ISSUES THAT BREAK COMPATIBILITY, and says it is in discussions with several companies about long-term stewardship. No transfer has been announced. The package is NOT deprecated on npm and latest is 2.3.2, published 2026-06-10, so the last release already predates the announcement, consistent with maintenance-only → FlashList stays the RN large-list default (maintained-for-compatibility is not broken), but a team starting a large list surface now should price in that the maintainer is leaving; Legend List is the actively-developed RN + web alternative. The tripwire fires on an npm deprecation flag (the usual signal of a rename or handover); a steward being NAMED is not machine-observable, so re-read the announcement thread when this entry is next challenged. The same announcement archives Restyle (not an option here) and hands react-native-skia to its author under a new name — RB-E-ANIMATION owns that thread.
PLAIN TEXT (facts verified vs the repo README + props guide; npm 0.9.0, 2026-09-25, verified 2026-10-01): react-native-plain-text is a cell-content micro-optimization, not a list engine. It bypasses RN's text layout pipeline by rendering to the platform's native label widget (UILabel / TextView) and buys that with a hard constraint: single string, single style, no nested <Text>, no press handling (wrap it in a Pressable) and no selection (listed as To Do). It requires the New Architecture and does not run in Expo Go → adopt it AFTER virtualization is right, and measure first. 0.9.0 adds a unified `Text` SELECTOR component: it renders `PlainText` for simple strings and falls back to RN's own `<Text>` for anything `PlainText` doesn't support (nesting, mixed styles), so existing code can adopt it without manually auditing which elements qualify — the per-element single-style/no-nesting constraint itself is unchanged.
TANSTACK TABLE V9 (9.0.0 stable on npm 2026-08-04, verified vs the registry and the two TanStack v9 posts): REACTIVITY moved underneath the Table APIs. Instead of one table-state object that adapters wrap from the outside, feature state is split into atoms (pagination, row selection, column sizing, …) behind a shared `TableReactivityBindings` contract, so each of its 9 framework adapters plugs in native signals (Angular/Solid/Vue) or TanStack Store; selecting a row updates that checkbox and the selected-row counter without making every cell depend on whole-table state. Callers still pass ordinary values and call the same Table methods, so this is an upgrade, not a rewrite. MEMORY: v8 built every row/cell/header object with its own copy of every method, each with its own closure scope; v9 hangs those methods on shared prototypes cached per table. On the authors' Playwright/CDP benchmark that is up to ~90% less memory on large tables, moving the practical browser ceiling from ~1–1.5M rows (at ~4GB) to roughly 10–16M. Their own edges: small tables (10 rows × 8 cols, all features) use slightly MORE memory in v9 because of added memoization, and the refactor carried one breaking change → "huge web tables → HighTable" is now a call about DOM/UX at that size, not about Table running out of memory first.
HISTORY: 2026-10-01 REOPENED: react-native-plain-text 0.9.0 adds a unified `Text` selector component (auto-falls back to RN `<Text>` for nesting/mixed styles) — adoption no longer needs a manual per-element audit. 2026-08-18 REOPENED: react-native-plain-text promoted from skip to a when-clause on its 2nd independent signal (TWiR #293, then RN Rewind #52) — the reopen condition recorded at the first skip. 2026-08-18 REOPENED: the TanStack Table v9 memory post, carried as a cap skip since React Status #486, was re-read and kept — it moves the HighTable threshold. 2026-08-04 GAP CLOSED: TanStack Table v9 went stable (the beta line had been a skip since #291).
Canonical reading
Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.
Why Legend List is fast, from its author: it mounts a fixed pool of absolutely-positioned containers once and never re-renders that array again — instead it *signals* individual containers to re-render themselves at a new position/item as you scroll, and turns frequent size changes into animated-style updates rather than list re-renders. The generalizable lesson ('render once'): keep each render tiny by pushing it to the leaf that actually changed. Complements the peterp.me engine walkthrough with the same-source mechanism; announces v3.0 stable (RN + web). Vendor talk.
The maintainer's architecture deep-dive on FlashList's New-Architecture rewrite — view recycling, progressive rendering, layout prediction, and pre-paint correction via synchronous measurement — explaining the recycling machinery from first principles, not just the API.
Cleanly separates the two long-list strategies — windowing (render only the viewport, mount/unmount on scroll) vs recycling (reuse nodes by reassigning keys) — and when to escalate. Bridges web (react-window) and RN (FlashList).
Explains Legend List's virtualization formula — estimated item size, draw distance, container-pool ratio, asymmetric 1.5x/0.5x buffering. A concrete mental model for list recycling performance (complements the FlashList piece).
The five techniques behind HighTable — lazy slicing, the ~17M-px canvas-height ceiling with downscaled scrollbars, dual local/global scroll modes, decoupled axes. The web huge-table counterpart to the RN list pieces above (HighTable is a listed option here).
The architecture rationale behind the v9 line this entry's note records: v8 kept one stable table instance and let each adapter connect it to a framework's update model from the outside, which makes any state change look like whole-table churn. V9 puts a reactive graph underneath instead — per-feature atoms plus a `TableReactivityBindings` contract each adapter fills with its framework's own primitives — so a row selection updates that row and the counter, not every cell. The generalizable lesson matches this entry's Legend List readings: the cost of a big grid is what you make re-render, so put the subscription grain at the leaf.
Depth (in-domain rules) is owned by the react-native-best-practices skill — this entry is selection breadth.
The full explanation
The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.
About lists & virtualization in React & React Native
Diataxis: Explanation. This page builds understanding of why long lists need
virtualization and which strategy each tool uses. It is not a tutorial, and the exact
thresholds (when FlashList, item-size rules) are owned by react-native-best-practices.
Read this for the why.
The one fact that organises everything: cost scales with mounted nodes, not data length
A list is cheap or expensive based on how many view/DOM nodes it mounts, not how many items
the array holds. Render 10,000 rows naively and you create 10,000 nodes — layout, memory, and
scroll all collapse. The whole field exists to break that coupling: render only what's on (or
near) the screen. Two strategies do it, and every library here is one of them:
Windowing — mount only the items in the viewport (plus a small buffer); as you scroll,
unmount what leaves and mount what enters. The node count stays ~constant regardless of data
size. (FlatList, react-window, react-virtual.)
Recycling — keep a small fixed pool of views and re-bind them to new data as they scroll
in/out, instead of unmounting and remounting. Cheaper still for big, uniform lists because it
avoids mount/unmount churn. (FlashList.)
So the first question is never "which list lib" — it's "how many nodes will this mount, and is
windowing enough or do I need recycling?"
The default, and why
React Native: FlashList for large lists, FlatList otherwise, ScrollView only for <~20
static items. Web: react-virtual / react-window.
The RN ladder is about matching strategy to scale. ScrollView mounts everything — fine for a
handful of static rows, a foot-gun beyond that. FlatList adds windowing — the correct baseline
for most lists. FlashList adds recycling — the win for large lists, where mount/unmount churn
dominates (its v2 is a New-Architecture rewrite; see the reading). On the web, the data isn't
native views but DOM nodes, and the same logic applies: react-window / react-virtual window
the viewport.
The landscape, and when each one wins
ScrollView / .map() — no virtualization; every item mounts. Correct only for small,
static lists (<~20). Using it for real data is the most common list mistake.
FlatList — RN's built-in windowing. The right default; tune-able, mature, and enough for the
majority of lists.
FlashList — recycling for large RN lists. Wins when lists are long and rows are uniform
enough to reuse; v2's ground-up New-Architecture rewrite handles recycling, progressive
rendering, and layout prediction (reading). Reach for it at scale, not reflexively. Note that
v2 runs only on the New Architecture — old-arch apps must pin v1.x (or stay on FlatList).
Stewardship (verified 2026-09-24). Shopify is leaving React Native and has put FlashList on
critical-compatibility-fixes only while it looks for a long-term steward; the latest release
(2.3.2) predates that announcement and npm carries no deprecation flag. FlashList stays the RN
large-list default, but a team starting a large list surface where maintainer continuity matters
more than maturity should pick Legend List, which ships actively on both platforms.
react-window / react-virtual / HighTable (web) — web windowing. react-window/react-virtual
cover normal long lists; HighTable handles billion-row tables with tricks the others don't
(working around the ~17M-px canvas-height ceiling with downscaled scrollbars, decoupled axes —
reading). Reach for HighTable specifically for huge tabular data.
Legend List — one virtualization engine across RN + web (v3 adds SectionList + chat
APIs). The pick when you want a single list mental model on both platforms.
Tradeoffs and failure modes to name out loud
ScrollView for real data. The cardinal sin: it mounts everything, so it's smooth at 15
rows and janks at 200. Match the tool to the count.
FlashList reflexively. Recycling shines for large, uniform lists; for short or wildly
heterogeneous lists, FlatList's windowing is simpler and fine. Don't reach for recycling before
you have the scale that needs it.
Fighting recycling with unstable items. Recycled rows assume reuse; per-row unstable state,
unkeyed content, or wildly variable heights undercut the pool and reintroduce churn.
Ignoring the thresholds. "How many items before FlashList," item-size estimates, and
render-perf rules are exact and owned by react-native-best-practices — this page gives the
model, that skill gives the numbers.
How it interacts with the rest of the stack
Charts/graphics (RB-E-CHARTS). Huge-data rendering shares DNA — at extreme scale (charts,
billion-row tables) the answer shifts from DOM/view virtualization toward canvas/GPU rendering.
Data (RB-E-DATA). Long lists usually mean paginated/infinite server data; the list's
windowing pairs with the data layer's useInfiniteQuery-style fetching.
Perf depth (react-native-best-practices). Exact thresholds, item-layout, and the
RN-RULE-USE-FLASHLIST guidance live there.
In one paragraph
A list's cost is the nodes it mounts, not the data length — so render only what's on screen,
via windowing (mount the viewport, unmount the rest) or recycling (reuse a fixed view
pool). On React Native: ScrollView only for tiny static lists, FlatList (windowing) as the
baseline, FlashList (recycling) at scale. On the web: react-window/react-virtual for normal
lists, HighTable for billion-row tables; Legend List when you want one engine across both. Don't
use ScrollView for real data, don't reach for recycling before you need it, and get exact
thresholds from react-native-best-practices.
See also: RB-E-CHARTS (canvas/GPU rendering at extreme data scale), RB-E-DATA (paginated /
infinite data behind long lists). Exact thresholds + render-perf rules: the
react-native-best-practices skill.