← Default view
Instrument panel · React data-grid load bench · 2026-07-13

Four channels.
Three in the red.
One is a wall.

Row count is almost never the constraint that breaks a React grid. Update frequency, referential stability, and the browser's scroll-container pixel cap are.

Cover mark for this research page
CH-01
Row count
NOMINAL
CH-02
Update frequency
REDLINE @ 10/s
CH-03
Referential stability
FAULT · LOOP
CH-04
Scroll-container height
HARD CEILING
CH-01 · NOMINAL

The needle that never moves

Virtualize above ~1k rows and the row count stops mattering. Every serious grid renders about forty rows, whether the dataset is ten thousand or ten million [10]. The trace you are looking for — DOM cost climbing with dataset size — does not exist. It was flattened in 2015 and people are still buying grids to fix it.

MOUNTED DOM ROWS ↑
VIRTUALIZED · ~40 rows · flat
UNVIRTUALIZED · what you fear
1k10k50k100k1M10M →
40
ROWS IN THE DOM
…at any dataset size. AG Grid's docs say the browser runs out of memory before the grid gets into trouble [10].
READOUT · 50,000 ROWS × 20 COLUMNS · 1200×600 VIEWPORT · 36px ROWSTHE ONE REPRODUCIBLE FIXTURE [1]
Grid JS gzip Ready (median) Scroll settle Mounted cells
React Data Grid ⭐ 7.7k 72.3 KB 367.6 ms 21.2 ms 264
TanStack Table + Virtual ⭐ 28k 78.7 KB 369.3 ms 21.7 ms 444
Ace Grid Core 224.5 KB 387.4 ms 28.1 ms 220
AG Grid Community ⭐ 15k 358.7 KB 443.2 ms 29.8 ms 360
Handsontable ⭐ 22k 380.9 KB 872.8 ms 49.8 ms 198

Read the flatness, not the ranking. Scroll settle is 21–30 ms across the entire field. The 76 ms spread between fastest and slowest (excluding Handsontable) is bundle parse and init — not scrolling. Nobody is slow at 50k rows. Below ~100k, pick on features. MUI X Community was excluded from the ranking outright: its Community tier cannot virtualize past 100 rows without pagination [6].

CH-02 · REDLINE

Updates per second — where the architecture shows

This is the channel that actually pegs, and it has nothing to do with how many rows you loaded. It is decided by whether a cell update enters the React reconciler at all.

MUI X Data Grid ⭐ 5.8k
0/s
100/s
~10/sec
DEGRADES TO UNUSABLE

Every cell is a React element. Users report the grid becoming "slow and nearly unusable" at ten row-updates per second on a 400-row window — where the plain HTML table it replaced handled a hundred [13]. The grid memoizes cells, but React.memo is defeated at the root the moment a caller passes an unstable non-primitive prop [14]. ⚠ MUI X is a display grid at scale, not a streaming grid.

AG Grid ⭐ 15k
0/s
100/s
50ms
DEFAULT COALESCE WINDOW

Framework-agnostic core; React only at the edges. applyTransactionAsync() coalesces streaming ticks into a single flush every 50 ms — the reconciler never sees them [12]. This, not row count, is why AG Grid is the trading-blotter default. Handsontable shares the same shape.

ROW-UPDATE THROUGHPUT · 400-ROW WINDOW · USER-REPORTEDCH-02
Plain HTML table
100 /sec
MUI X Data Grid
~10 /sec

The grid you adopted to handle "big data" is ten times slower at updating four hundred rows than the <table> you deleted [13]. Separately: an open issue reports DataGridPro leaking ~199 MB per refresh at 100k rows, reaching 4.7 GB, with retained CellWidget instances implicating renderCell [28].

CH-03 · FAULT

The failure is referential, not algorithmic

Headless means zero built-in re-render cost and zero built-in protection. The classic TanStack Table ⭐ 28k blow-up is not a slow render path — it is a new array identity on every render [22].

// memoized the base array — still broken
const rows = useMemo(() => fetchRows(), []);

const table = useReactTable({
  data: rows.filter(r => r.active),   // ← new array, every render
  columns,
});

// data changes identity → table re-renders
// → .filter() runs again → new array
// → data changes identity → ∞
INFINITE RENDER LOOP
Not a performance cliff — a hang. And the React Compiler does not save you: it auto-memoizes existing code, but a freshly-constructed array is new by construction, not by a missed memo [27] [22].
⚠ COUNTER-MEASURE MISFIRE · MEMOIZATION IS NOT FREECH-03

The instinct to memo() your way out is a trap. Material React Table's own guide is blunt: memoMode: 'table-body' disables virtualization and most features, leaving a table "mostly frozen after first render"; memoizing rows costs you access to table state like getIsSelected() [23]. Memoize the data, not the render tree.

CH-04 · HARD LIMIT

The ceiling nobody markets

Browsers truncate an element's height. MUI documents 17.5M px on Firefox and 33.5M px on Chrome/Edge/Safari [6]; the open TanStack Virtual issue cites 33,554,400 / 33,554,428 [7] ⭐ 7.0k; a third measurement puts Chromium at 16,777,216 [8]. The figure is engine- and version-dependent — budget for the low end, ~16.7M px. Past it the container is silently clipped and the tail of your data is unreachable. The virtualizer does not error. It just stops.

SCROLL-CONTAINER PIXEL CAP · ~16,777,216 px (BUDGET) · 33,554,432 px (BEST CASE)
~760kROWS AT 16.7M PX
22 px
~1.5M @ 33.5M
~520kROWS AT 16.7M PX
32 px
~1.0M @ 33.5M
~465kROWS AT 16.7M PX
36 px
~930k @ 33.5M
~300kROWS AT 16.7M PX
56 px
Material · ~600k @ 33.5M
SAMECANVAS IS NOT EXEMPT
Canvas
native scrolling → same cap
Every column hits the same ceiling. What differs is only how many rows that buys you. Glide Data Grid ⭐ 5.3k uses native scrolling [16] — canvas fixes paint cost, not scroll geometry.
ESCAPE 01 · BORING & CORRECT

Server-side row model

AG Grid's SSRM demo serves 10M records and moves the constraint to your backend [11].

ESCAPE 02 · YOU OWN IT NOW

Custom scrollbar

Replace native scrolling; position rows absolutely against a synthetic scroll position [8].

ESCAPE 03 · DOWNSCALE

HighTable

Caps the canvas at ~8M px, then switches to a global-anchor + local-offset model — claiming 1-px fidelity to ~2 trillion rows at 30 px [9].

UPSTREAM STATUSUNRESOLVED

The TanStack Virtual issue is still open [7]. A headless virtualizer ships indices and offsets, not a scrollbar — so this is explicitly your problem [20]. Treat any "millions of rows" claim as marketing unless the grid has replaced native scrolling.

CALIBRATION

Every instrument here was supplied by someone selling a grid

Read this before you trust any number above. Every published 2026 benchmark is vendor-authored, and they do not merely flatter their authors — they contradict each other outright.

⚠ CONFLICT OF INTEREST · CALIBRATION UNVERIFIED
BenchmarkAuthorAlso shipsWinnerFixture
1771 Technologies [3] 1771 Technologies LyteNyte Grid LYTENYTE — ITS OWN not published
Battle of the Rows [5] Revolist OU RevoGrid REVOGRID — ITS OWN not published
react-data-grid-benchmark [1] Vitashev [2] ⭐ 0 Ace Grid REACT DATA GRID — NOT ITS OWN fixture + lockfile + raw samples
REVOGRID, ON AG GRID [5]
1,297 MB at 100k rows.
Tab crashed at 400k.
1771 TECHNOLOGIES, ON AG GRID [3]
Scrolls 1M rows at 27 FPS.
No crash.
BOTH CANNOT BE TRUE · SAME LIBRARY · SAME YEAR

Trust the one that ships a reproducible fixture, a package lock and raw samples — currently only Vitashev's ⭐ 0, which publishes its conflict of interest in the README and still doesn't put its own grid first [2]. And vendor FPS on an M4 MacBook Air [3] tells you nothing about your users' 2019 ThinkPads.

AUX · RENDERER

DOM vs canvas — the dealbreaker isn't architectural

CHANNEL COMPARISONDOM + VIRTUALIZATION vs CANVAS
DOM (+ virtualization)Canvas — Glide ⭐ 5.3k
PAINT AT SCROLL Create/destroy N nodes per frame — the argument Glide makes against DOM [15] Repaint dirty rects; flat memory 100 rows → 10M rows [16]
HIGH-FREQ UPDATES Reconciler-bound Vendor claims 100k+ updates/sec [16] — unverified by any third party
TEXT SELECT / CTRL-F ✓ free ✗ must be re-implemented [18]
ACCESSIBILITY ✓ real semantics (though pinned columns + virtualization mangle DOM order anyway) ⚠ hidden mirror DOM; maintainers admit "none of the primary developers are accessibility users so there are likely flaws" [15]
CSS / THEMING ✗ draw calls
MAINTENANCE LATEST STABLE 6.0.3 · 2024-02-03 [17]

No stable release in 29 months. Adopting a canvas grid in 2026 means owning a canvas renderer [17]. The practitioner view is also less one-sided than the README: canvas wins scroll and streaming, loses layout primitives, and a well-built DOM grid closes much of the gap [18].

AUX · VIRTUALIZER

This choice is not a performance decision

Only relevant if you pair a headless table with a virtualizer — bought grids have one built in and you should not layer another on top. All three hold 60 FPS at 100k items; initial render differs by single-digit milliseconds [19]. It's an API-surface decision.

LibraryBundleLatestDynamic heightsVerdict
@tanstack/react-virtual ⭐ 7.0k ~5 KB 3.14.6 · Jul 2026 measureElement ref Default if already on TanStack Table; headless, no markup [20]
react-virtuoso ⭐ 6.4k ~17 KB 4.18.10 · Jun 2026 auto · ResizeObserver Fewest footguns; sticky groups + bidirectional infinite scroll built in
react-window ⭐ 17k ~6 KB 2.2.7 · Feb 2026 useDynamicRowHeight ⚠ Stop repeating the "abandoned, fixed-height-only" line — v2 is a full rewrite [21]

Virtualization only earns its overhead somewhere around 50+ simultaneously-rendered rows. Column virtualization is where wide grids quietly die: don't bother under ~12–20 columns [24], but note that MUI X disables column virtualization entirely when dynamic row height is on [6] — wide + auto-height means every column mounted.

LIMITS

Practical ceilings, per unit

AG Grid — client-side
⭐ 15k
Comfortable 100k+ [10] · Degrades ~1M at 27 FPS (vendor-measured) [3] · Hard stop browser memory / scroll cap
AG Grid — SSRM
SERVER-SIDE ROW MODEL
Comfortable 10M+ [11] · Hard stop your backend
TanStack Table + Virtual
⭐ 28k · v8.21.3, Apr 2025
Comfortable 100k+ [1] · Degrades when you mis-memoize · Hard stop scroll cap — unsolved upstream [7]. Note v9 has not landed [26].
React Data Grid
⭐ 7.7k · LEANEST BUNDLE
Comfortable 50k measured, 72.3 KB gzip, 264 mounted cells [1] · Hard stop still 7.0.0-beta after years
Comfortable 10k–100k display-only · Degrades ~10 updates/sec [13]; 1M scroll ≈ 33 FPS (vendor) [3] · Hard stop Community caps virtualization at 100 rows [6]; ⚠ open 199 MB/refresh leak at 100k [28]
Comfortable ≤ ~150k; docs steer you to pagination [25] · Degrades 872 ms ready at 50k — 2.4× the field [1] · Hard stop OOM crash reported at 200k–500k [4] and 400k [5]
⭐ 5.3k · CANVAS
Comfortable millions (vendor) [16] · Hard stop ⚠ unmaintained since Feb 2024 [17] — and the scroll cap applies anyway
TEST POINTS

Probe these four, on your data

01

Mounted cell count after scrolling to the middle

The honest proxy for virtualization quality — and the one number no marketing page prints [1].

02

Updates/sec before frame drops

The metric that actually separates AG Grid from MUI X [12] [13].

03

rowCount × rowHeight vs 16.7M px

A pure-arithmetic go/no-go on native scrolling. Do this one before you write any code [8].

04

Heap after 5 minutes of data refresh

An open MUI X issue leaks ~199 MB per refresh at 100k rows, reaching 4.7 GB [28] ⭐ 5.8k.

SOURCE RACK

28 inputs · vendor-flagged

01
VENDOR-BLOG · reproducible
06
OFFICIAL · the pixel cap
10
OFFICIAL · the ~40 rows
12
OFFICIAL · 50 ms flush
14
15
OFFICIAL · ⭐ 5.3k
16
VENDOR-BLOG · unverified claims
17
OFFICIAL · 6.0.3 · 2024-02-03
20
21
OFFICIAL · ⭐ 17k · v2.2.7
22
OFFICIAL · ⭐ 28k · the loop
25
OFFICIAL · ⭐ 22k
26
OFFICIAL · 8.21.3 · Apr 2025
27
OFFICIAL · won't save you
28
FORUM · ⭐ 5.8k · open
BUS

Other channels on this expedition