Benchmarking Infinite Table, MUI X Data Grid, AG Grid and TanStack Table with a million rows
By raduยท
With release 10.1.4 we improved Infinite Table rendering to make it possible to render huge amounts of data in your favorite React DataGrid. Previously, we were capped at about 400k records, but now you can go much higher, so we wanted to test 1 million records and see if performance stays sharp with this many records. So we put it to work and compared it against three other React DataGrids: MUI X Data Grid, AG Grid and TanStack Table.
We build Infinite Table, so you should read any benchmark we publish with healthy suspicion. That's exactly why we made this one open: the code, the raw numbers, and the exact commands are in the datagrid-performance-comparison repository, and you can run the whole thing on your own machine in about an hour.
The goal was not to declare a winner. It was to measure where each grid spends its time - and Infinite Table is not ahead everywhere.
The short version:
- Sorting: Infinite Table is the fastest of the four in every case we measured, by 1.6ร to 3.3ร.
- Memory: Infinite Table keeps the least - 91 MB for a million rows, against 150 MB for MUI X Data Grid, 213 MB for AG Grid and 287 MB for TanStack Table.
- Mounting: with a million rows Infinite Table is the first to show them. With 300k rows TanStack Table is 6% faster than Infinite Table, which is faster than the other two.
- Scrolling: TanStack Table, rendered with TanStack Virtual, does the least work while scrolling vertically and is the fastest to jump to a far-away row - with a much simpler page than a full grid. Infinite Table does the least work scrolling horizontally at 300k rows, and never shows blank rows; it also had the longest single frame while scrolling vertically.
- A million rows: only Infinite Table and AG Grid can scroll to the last one. MUI X Data Grid and TanStack Table stop at row 838,860.
At a glance - 1,000,000 rows
median of 7 runs ยท lower is better
Mount: first rows painted
Sort a string column
Sort a number column
View as table
| At a glance - 1,000,000 rows | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| Mount: first rows painted | 745 ms | 808 ms | 659 ms | 565 ms |
| Sort a string column | 5,466 ms | 4,067 ms | 6,083 ms | 2,120 ms |
| Sort a number column | 2,596 ms | 3,306 ms | 4,510 ms | 1,374 ms |
The four grids#
Infinite Table and AG Grid are full data grids that show all the rows in one scrollable view. The other two need a few words.
MUI X Data Grid: all the rows on one page#
We used the free, MIT-licensed
DataGrid. It always paginates, and by default a page has 100 rows. To make it show what the other grids show, we gave it a page size of -1, the value its documentation gives for "all results". Every row is then on one page, which the grid virtualizes and scrolls through like the others do. Its pagination footer stays, and reads "1โ1,000,000 of 1,000,000".TanStack Table: headless, with our markup#
TanStack Table has the row model and the sorting, and renders nothing. What we measured is what its documentation recommends for large tables: TanStack Table, TanStack Virtual for the rows and the columns, and plain
<table> markup. The page follows the official virtualized-columns example, with the same overscan.That page is much less than a grid. It has no keyboard navigation, focus, cell or row selection, column menus, resizing, reordering or pinned columns, and it renders 352 DOM elements where the other three render 529 to 637. Doing less on every frame is the point of a headless library, and it shows in the scrolling numbers.
The setup#
Every grid renders the same data, in the same browser, with the same interactions. We kept each grid on its defaults and only set what was needed to make the layouts identical.
- Data: 300,000 and 1,000,000 rows ร 20 columns, generated deterministically. Every grid imports the same module, and the harness verifies a checksum, and that all the grids show the same text, before every run.
- Configuration: same row height, header height, and column widths, and all the rows in one scrollable view. No other tuning, except:
- MUI X Data Grid has the page size of
-1described above, and shows numbers as they are - by default it formats them for the locale ("100,000.13"). - AG Grid uses the Quartz theme with
animateRows={false}(the other grids don't animate rows on sort, and the animation would make "sorted rows visible" ambiguous) and agetRowId(the other grids have a row id too). - TanStack Table has
sortDescFirst: false: by default its first click on a number column sorts descending, and in the other grids it sorts ascending. Its rows have a fixed height, so nothing is measured while scrolling.
- MUI X Data Grid has the page size of
- Versions: all four are installed from npm: Infinite Table 10.1.5 (
@infinite-table/infinite-react), MUI X Data Grid 9.17.0 (@mui/x-data-grid), AG Grid React Community 36.3.0 (ag-grid-reactandag-grid-community) and TanStack Table 9.3.1 (@tanstack/react-table) with TanStack Virtual 3.14.26. Each page loads only its own grid. - License keys: none. Infinite Table works the same without one, but shows a "Powered by Infinite Table" footer, and MUI X Data Grid has its pagination footer. The benchmark makes the boxes taller until every grid scrolls a body of the same height, and renders the same number of rows.
- Builds: production builds only - the benchmark refuses to run against a dev server.
- Browser: Chrome 136, headless, sandboxed like the Chrome your users run,
en-USlocale, no CPU throttling, 1400ร1000 viewport. - Machine: a GitHub Actions hosted runner (Ubuntu 24.04, 4 cores of an AMD EPYC 9V74, no GPU). It is slower than a recent laptop, and anyone can start the same run on the same kind of machine.
- Runs: 7 measured runs per grid per scenario, plus 1 discarded warmup. The grids are interleaved - each run goes through all of them, in an order that changes from one run to the next - and every run uses a fresh browser context.
How we measure#
Latency is read from Chrome performance traces, the same way js-framework-benchmark does it: from the input event (
pointerdown, setState, or setting scrollTop) to the end of the first frame Chrome commits where the DOM shows the expected content - the right first and last visible rows and columns, at the right place.Scrolling uses Chrome's real input pipeline (
Input.synthesizeScrollGesture), so every grid gets exactly the same wheel gesture, with threaded scrolling enabled. Frame outcomes come from Chrome's compositor: a frame is either fully presented (it includes the grid's latest main-thread update), presented without the main-thread update (the compositor scrolled what it already had), or dropped.For every metric we report medians. Infinite Table is compared with each of the other grids, with a 95% bootstrap confidence interval of the difference; we only call a difference when that interval excludes "no difference".
Every chart below has a "View as table" link with its exact numbers, and the full report has every metric of every scenario.
Mounting the grid#
Time from
setState with all rows already in memory to the first frame showing the full viewport, and until the grid stops changing the DOM.Mount time
median of 7 runs ยท lower is better
First rows painted, 300k rows
Settled (no more DOM changes), 300k rows
First rows painted, 1M rows
Settled, 1M rows
View as table
| Mount time | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| First rows painted, 300k rows | 275 ms | 323 ms | 232 ms | 246 ms |
| Settled (no more DOM changes), 300k rows | 275 ms | 425 ms | 232 ms | 246 ms |
| First rows painted, 1M rows | 745 ms | 808 ms | 659 ms | 565 ms |
| Settled, 1M rows | 745 ms | 911 ms | 659 ms | 567 ms |
With a million rows Infinite Table paints the first rows sooner than the other three: 14% sooner than TanStack Table, 24% sooner than MUI X Data Grid and 30% sooner than AG Grid. With 300k rows TanStack Table is the fastest, 6% ahead of Infinite Table, which is 11% ahead of MUI X Data Grid and 24% ahead of AG Grid. AG Grid keeps changing the DOM for another 100 ms after its first paint; the other three are done when the rows are painted.
Memory: JavaScript heap retained by the grid
after garbage collection, median of 7 runs ยท lower is better
300k rows
1M rows
View as table
| Memory: JavaScript heap retained by the grid | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| 300k rows | 53.9 MB | 67.4 MB | 86.6 MB | 36.6 MB |
| 1M rows | 150 MB | 213 MB | 287 MB | 90.7 MB |
Infinite Table retains the least memory at both sizes. At 1M rows it keeps 40% less than MUI X Data Grid, 57% less than AG Grid and 68% less than TanStack Table, which creates a row object for every one of the million rows. The DOM is not where the difference comes from - the grids render a few hundred elements each - it is in the data structures each one builds over the rows.
Sorting#
Click a column header, measured until every visible row shows sorted, consistent data. We sorted a string column and a number column, ascending (the first, cold sort) and then descending.
Sorting 300,000 rows: click to sorted rows painted
median of 7 runs ยท lower is better
String column, ascending
String column, descending
Number column, ascending
Number column, descending
View as table
| Sorting 300,000 rows: click to sorted rows painted | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| String column, ascending | 1,275 ms | 969 ms | 1,354 ms | 487 ms |
| String column, descending | 1,205 ms | 1,008 ms | 1,354 ms | 571 ms |
| Number column, ascending | 649 ms | 879 ms | 1,083 ms | 398 ms |
| Number column, descending | 886 ms | 861 ms | 1,019 ms | 389 ms |
Sorting 1,000,000 rows: click to sorted rows painted
median of 7 runs ยท lower is better
String column, ascending
String column, descending
Number column, ascending
Number column, descending
View as table
| Sorting 1,000,000 rows: click to sorted rows painted | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| String column, ascending | 5,466 ms | 4,067 ms | 6,083 ms | 2,120 ms |
| String column, descending | 5,478 ms | 4,141 ms | 5,848 ms | 2,431 ms |
| Number column, ascending | 2,596 ms | 3,306 ms | 4,510 ms | 1,374 ms |
| Number column, descending | 3,760 ms | 3,379 ms | 4,527 ms | 1,479 ms |
Infinite Table is the fastest in all sixteen combinations: 1.6-2.6ร faster than MUI X Data Grid, 1.7-2.4ร faster than AG Grid and 2.4-3.3ร faster than TanStack Table. Almost all of this time is JavaScript on the main thread for every grid, so this is a measure of the sorting pipeline, not of rendering.
The browser ran with the
en-US locale, which matters when comparing strings. In that locale, sorting 1M rows by the string column (ascending) took 2120 ms in Infinite Table, 4067 ms in AG Grid, 5466 ms in MUI X Data Grid and 6083 ms in TanStack Table.Jumping around the dataset#
Ten programmatic jumps to far-away scroll positions, similar to dragging the scrollbar thumb.
Jumping to a far-away scroll position
jump to rows painted, median of 10 jumps ยท lower is better
300k rows
1M rows
View as table
| Jumping to a far-away scroll position | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| 300k rows | 23.6 ms | 34.9 ms | 12.2 ms | 35.3 ms |
| 1M rows | 21.8 ms | 34.9 ms | 12.5 ms | 40 ms |
TanStack Table is the clear winner here: the rows are on screen 12 ms after the jump. MUI X Data Grid needs 22-24 ms, and AG Grid and Infinite Table about 35 ms - level with each other at 300k rows, while at 1M rows AG Grid is 5 ms faster than Infinite Table.
Then there is a problem you only hit at this scale: 1,000,000 rows ร 40 px is 40 million pixels, taller than browsers allow an element to be (about 33.5 million px in Chrome on a standard-density display, and half of that on a 2ร display).
- Infinite Table and AG Grid map the scrollbar onto the rows - 1.25 content pixels per scrollbar pixel in this run - and the last row is reachable in both.
- MUI X Data Grid and TanStack Virtual don't: the element is cut at the browser's limit, and the last row you can scroll to is 838,860. The 161,140 rows after it are loaded and sorted, but can't be seen. Their 1M scrolling numbers are for the part that can be reached.
Scrolling smoothness#
A wheel gesture scrolling 24,000 px down at 8,000 px/s, and horizontal sweeps across all columns at 4,000 px/s.
Scrolling horizontally, all four grids hold 60 fps at both sizes, and the slowest frame of a typical run is one refresh interval (17 ms).
Scrolling vertically, MUI X Data Grid, AG Grid and TanStack Table hold 59-60 fps. Infinite Table is at 59 fps with 300k rows and at 55.5 fps with a million, and it has the longest single frame of the four: 50 ms at 300k rows and 117 ms at 1M, where the others stay at 17 ms (AG Grid: 33 ms at 1M). That is one hitch in a three-second gesture, on a machine without a GPU; it is the first thing we will look at.
Infinite Table does get more of its frames fully presented while scrolling vertically - frames that include the grid's latest update, instead of the compositor scrolling what it already had:
Vertical scrolling: frames fully presented
share of frames that include the grid's latest update ยท higher is better
300k rows
1M rows
View as table
| Vertical scrolling: frames fully presented | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| 300k rows | 50.3% | 51.1% | 50.3% | 59.1% |
| 1M rows | 50.3% | 49.9% | 50.3% | 65.2% |
The other difference is in how much work the main thread does to keep up - which matters on slower machines and when your own cell renderers add to it.
Vertical scrolling, 300k rows: main-thread work
total over a 3-second gesture, median of 7 runs ยท lower is better
Scripting
Layout
Style recalculation
All tasks
View as table
| Vertical scrolling, 300k rows: main-thread work | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| Scripting | 677 ms | 660 ms | 428 ms | 771 ms |
| Layout | 72 ms | 111 ms | 92.1 ms | 87.8 ms |
| Style recalculation | 100 ms | 296 ms | 49.5 ms | 202 ms |
| All tasks | 1,625 ms | 1,630 ms | 1,013 ms | 1,739 ms |
Scrolling vertically, TanStack Table does about 40% less work than the three full grids. Among those, Infinite Table does the most at 300k rows - 7% more than MUI X Data Grid and AG Grid, because it runs more JavaScript per frame, even though it does less layout and style recalculation than AG Grid. At 1M rows MUI X Data Grid does the least of the three, and Infinite Table 3% less than AG Grid.
Horizontal scrolling, 300k rows: main-thread work
total over 4 sweeps across all columns, median of 7 runs ยท lower is better
Scripting
Layout
Style recalculation
All tasks
View as table
| Horizontal scrolling, 300k rows: main-thread work | MUI X Data Grid | AG Grid | TanStack Table | Infinite Table |
|---|---|---|---|---|
| Scripting | 401 ms | 270 ms | 177 ms | 165 ms |
| Layout | 49.1 ms | 78.9 ms | 54.6 ms | 11.9 ms |
| Style recalculation | 24.5 ms | 46.7 ms | 15.7 ms | 53.4 ms |
| All tasks | 749 ms | 721 ms | 538 ms | 495 ms |
Scrolling horizontally at 300k rows, Infinite Table does the least work: 8% less than TanStack Table and about a third less than AG Grid and MUI X Data Grid, with 4-7ร less layout than any of them. At 1M rows TanStack Table does the least, 17% less than Infinite Table, which stays ahead of AG Grid (by 14%) and MUI X Data Grid (by 29%).
What you see during a very fast scroll#
This is where the grids make genuinely different design choices.
When you flick the wheel faster than a grid can re-render, it has to show something for the rows it hasn't caught up with. There are two reasonable options:
- Show empty space until the right content is rendered. Nothing on screen is ever wrong, but you can see gaps at the edge of the viewport.
- Move the existing rows into place immediately and update their content on the next render. The viewport is always full, but for a frame or two some rows can show the content they held before.
AG Grid and TanStack Table take the first approach. Infinite Table takes the second for vertical scrolling: it repositions rows as soon as the scroll happens, without waiting for React to commit the new content, so that strong inertial scrolling never reveals blank areas. For horizontal scrolling it waits for the commit.
The benchmark measures both effects during a 20,000 px/s flick. At 1M rows, 53% of the frames captured from AG Grid and 10% of those from TanStack Table had blank rows, and none of those from Infinite Table or MUI X Data Grid. In Infinite Table, most of the points sampled in its rows during the flick showed a row's earlier content; MUI X Data Grid kept up with neither blank nor stale rows. All of those numbers are in the full report.
What we take away from this#
Where Infinite Table came out ahead of all three:
- Sorting: 1.6-2.6ร faster than MUI X Data Grid, 1.7-2.4ร faster than AG Grid, 2.4-3.3ร faster than TanStack Table (strings sorted in an
en-USbrowser) - Memory: 32-40% less than MUI X Data Grid, 46-57% less than AG Grid, 58-68% less than TanStack Table
- Mounting a million rows: 14-30% sooner
- Main-thread work while scrolling horizontally at 300k rows
- More frames fully presented while scrolling vertically
- Reaching the last of a million rows, which only AG Grid also does
Where MUI X Data Grid came out ahead of Infinite Table:
- Jumping to a far-away row: 22-24 ms against 35-40 ms
- Vertical scrolling: 7-19% less main-thread work, a steady 60 fps, and no long frame
- A very fast scroll with neither blank nor stale rows
Where AG Grid came out ahead of Infinite Table:
- Vertical scrolling: 59-60 fps and a shorter longest frame
- A faster median jump at 1M rows (by 5 ms)
- 6% less main-thread work scrolling vertically at 300k rows
Where TanStack Table (with TanStack Virtual) came out ahead of Infinite Table:
- Jumping to a far-away row: 12 ms against 35-40 ms
- Vertical scrolling: about 45% less main-thread work, and a steady 60 fps
- Mounting 300k rows 6% faster, and less work scrolling horizontally at 1M rows
It does that with a page that is not a full grid.
For us, there are two clear to-dos, both in vertical scrolling: the long frame, and the JavaScript we run per scrolled frame. We're glad to see where we're ahead, and just as glad to have a precise, repeatable measure of where we're not.
Limitations#
A benchmark is a model of reality, not reality. Please keep these in mind:
- One machine, one browser. A shared cloud machine with 4 cores and no GPU. A laptop will be faster in absolute terms, and its frame pacing will be different. Compare the ratios between the grids, not the absolute numbers.
- Headless Chrome. These numbers come from Chrome's new headless mode, which runs the same rendering code as headed Chrome. Frame-pacing numbers can still differ in a headed browser with a real display.
- Defaults only. Every grid has options that could change these results. We deliberately didn't tune any of them, because tuning one grid well and another badly is the easiest way to make a benchmark unfair.
- Not four of the same thing. The TanStack Table page is an example with our markup, not a full grid; a different way of rendering TanStack Table would give different numbers. MUI X Data Grid is on one page that holds every row, which is not its default.
- One dataset shape. 20 columns of plain text and numbers, no custom cell renderers, no grouping, no filtering, no live updates. Real apps do more.
- One browser locale. The string sorting result is for an
en-USbrowser. - We have an interest. We make Infinite Table. We've tried to design the harness so that it measures every grid the same way, and to report every metric - including the ones where another grid is ahead - but you shouldn't have to trust us. That's what the next section is for.
Run it yourself#
The benchmark has its own repository: infinite-table/datagrid-performance-comparison. All four grids are installed from npm, so there is nothing to build but the benchmark pages. Its README also has the results for the latest versions: the benchmark runs again on GitHub Actions whenever a new version of Infinite Table or one of the grids it is compared with is published. You need git and Node.js 20 or newer (we used 22); it runs on macOS and Linux. Playwright downloads its own Chromium, so it doesn't matter which browser you have installed.
1. Get the code and install#
2. Run it#
A full run takes about an hour: 9 scenarios ร 2 sizes ร 4 grids ร 8 runs. Without
BENCH_HEADLESS=1 it opens real browser windows - closer to what users see, but you have to leave the machine alone while they come and go.For numbers you can trust, close other apps, keep a laptop plugged in, and don't use the machine during the run.
To try it first, this takes about a minute:
3. Read the results#
Every run writes two files to
benchmark-results/<run id>/:report.md- one table per scenario, with the median and standard deviation for each grid and, for Infinite Table against each of the others, the ratio, its 95% confidence interval, and which grid is better when the difference is significantresults.json- every raw sample of every run
The most recent report is also copied to
benchmark-results/latest.md.Compare other versions#
The versions that are measured are the ones installed in
node_modules, and the report records them. To compare other releases of any of the grids, install them and run the benchmark again:Infinite Table reaches the last of a million rows starting with version 10.1.4. To measure only some of the grids, name them:
BENCH_GRIDS=mui,infinite.Options#
| Variable | Default | |
|---|---|---|
BENCH_SIZES | 300k,1m | dataset sizes to run |
BENCH_SCENARIOS | all | mount, sort-string, sort-number, scroll-jump, scroll-vertical, scroll-horizontal, correctness-vertical, correctness-horizontal, visual-blank-vertical |
BENCH_GRIDS | all | mui, ag-grid, tanstack, infinite - at least two |
BENCH_RUNS | 7 | measured runs per grid per scenario |
BENCH_HEADLESS | off | 1 runs without opening browser windows |
BENCH_CPU_THROTTLE | 1 | 4 slows the CPU 4ร, closer to a mid-range laptop |
The README documents the rest, along with the methodology of every scenario.
See the grids yourself#
The pages the benchmark measures are ordinary pages you can open and scroll yourself. After a build:
Then open localhost:3000 for the list of pages:
/benchmark/infinite-1m, /benchmark/mui-1m, /benchmark/ag-grid-1m and /benchmark/tanstack-1m (or -300k). If port 3000 is taken, pick another one with npm run benchmark:serve -- -p 4000.The full report#
The report and raw data behind this post:
Both are also in the benchmark repository, under
results/blog-post-2026-11-12.Help us make it fairer#
If you find a flaw in the methodology, a setting that unfairly favors one grid, or a scenario that's missing, please open an issue or send a pull request. That goes double for the MUI X, AG Grid and TanStack teams: if there's a configuration you'd recommend for a fair comparison - or a better way to render TanStack Table - we'll gladly add it, re-run the numbers, and update this post. You can also reach us at admin@infinite-table.com.