Benchmarking Infinite Table and AG Grid with a million rows
By raduยท
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 public, and you can run the whole thing on your own machine in under half an hour.
We compared Infinite Table with AG Grid Community - the most widely used data grid on the web, and a genuinely impressive piece of engineering. The goal was not to declare a winner. It was to measure where each grid spends its time.
The short version: Infinite Table is faster to mount, uses about half the memory, and sorts large datasets roughly 2.5ร faster. Scrolling is equally smooth in both, at a steady 60 fps.
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 | AG Grid | Infinite Table |
|---|---|---|
| Mount: first rows painted | 476 ms | 329 ms |
| Sort a string column | 3,771 ms | 1,410 ms |
| Sort a number column | 3,594 ms | 1,359 ms |
The setup#
Both grids render 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 two layouts identical.
- Data: 300,000 and 1,000,000 rows ร 20 columns, generated deterministically. Both grids import the same module, and the harness verifies a checksum before every run.
- Configuration: same row height, header height, and column widths. AG Grid uses the Quartz theme with
animateRows={false}(Infinite Table doesn't animate rows on sort, and the animation would make "sorted rows visible" ambiguous) and agetRowId(Infinite Table always has a primary key). No other tuning on either side. - Versions: AG Grid Community 36.3.0. Infinite Table built from source at version 10.1.3 .
- Builds: production builds only - the benchmark refuses to run against a dev server.
- Browser: Chrome 136, headless, sandboxed like the Chrome your users run, no CPU throttling, 1400ร1000 viewport.
- Machine: Apple M1 Pro (10 cores), macOS.
- Runs: 7 measured runs per grid per scenario, plus 1 discarded warmup. The two grids are interleaved (A/B, alternating which goes first), 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 both grids get 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 and a 95% bootstrap confidence interval of the difference between the two grids. We only call a difference when that interval excludes "no difference"; everything else is reported as a tie.
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 | AG Grid | Infinite Table |
|---|---|---|
| First rows painted, 300k rows | 210 ms | 134 ms |
| Settled (no more DOM changes), 300k rows | 313 ms | 141 ms |
| First rows painted, 1M rows | 476 ms | 329 ms |
| Settled, 1M rows | 580 ms | 345 ms |
Infinite Table paints the first rows about a third sooner, and is done 40-55% sooner.
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 | AG Grid | Infinite Table |
|---|---|---|
| 300k rows | 67.5 MB | 36.2 MB |
| 1M rows | 213 MB | 89.4 MB |
Infinite Table retains roughly half the memory - 46% less at 300k rows and 58% less at 1M. Both grids render a similar number of DOM elements (637 vs 607), so the difference is in the data structures each one builds over the rows, not in what ends up on screen.
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 | AG Grid | Infinite Table |
|---|---|---|
| String column, ascending | 743 ms | 324 ms |
| String column, descending | 780 ms | 336 ms |
| Number column, ascending | 810 ms | 330 ms |
| Number column, descending | 786 ms | 322 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 | AG Grid | Infinite Table |
|---|---|---|
| String column, ascending | 3,771 ms | 1,410 ms |
| String column, descending | 3,760 ms | 1,618 ms |
| Number column, ascending | 3,594 ms | 1,359 ms |
| Number column, descending | 3,496 ms | 1,305 ms |
Infinite Table sorts 2.3-2.7ร faster in every combination we measured. Almost all of this time is JavaScript on the main thread for both grids, so this is a measure of the sorting pipeline, not of rendering.
Jumping around the dataset#
Ten programmatic jumps to far-away scroll positions, similar to dragging the scrollbar thumb.
This one is a tie. Both grids show the right rows about 25 ms after the jump, at both 300k and 1M rows. (At 300k rows Infinite Table's slowest jump was faster - 29 ms vs 41 ms - but at 1M rows that difference disappears.)
Both grids also handle 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). Both map the scrollbar onto the rows the same way - 1.25 content pixels per scrollbar pixel in this run - and the last row is reachable in both.
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.
At 300k rows the two grids are equally smooth: both hold 60 fps on the main thread, the slowest frame is 17.6-17.7 ms for both, and neither has a single long animation frame or dropped frame in any run. At 1M rows both stay at 59-60 fps with no significant difference.
Infinite Table gets a few more frames fully presented when 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 | AG Grid | Infinite Table |
|---|---|---|
| 300k rows | 52.3% | 56.7% |
| 1M rows | 55.8% | 62% |
Where the grids differ more 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 | AG Grid | Infinite Table |
|---|---|---|
| Scripting | 393 ms | 478 ms |
| Layout | 75.8 ms | 54.8 ms |
| Style recalculation | 176 ms | 124 ms |
| All tasks | 992 ms | 1,052 ms |
Scrolling vertically, AG Grid runs about 18% less JavaScript, while Infinite Table does about 30% less layout and style recalculation. In total AG Grid does 6% less main-thread work at 300k rows; at 1M rows the totals are a tie.
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 | AG Grid | Infinite Table |
|---|---|---|
| Scripting | 170 ms | 106 ms |
| Layout | 51.7 ms | 8.33 ms |
| Style recalculation | 30.3 ms | 34.3 ms |
| All tasks | 432 ms | 325 ms |
Scrolling horizontally, Infinite Table does about 25% less main-thread work in total: 38% less scripting and 6ร less layout, with AG Grid slightly ahead on style recalculation. The 1M-row numbers follow the same pattern.
What you see during a very fast scroll#
This is where the two grids make genuinely different design choices.
When you flick the wheel faster than any 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 takes 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; those numbers are in the full report.
What we take away from this#
Where Infinite Table came out ahead:
- Faster mount: about a third less time to first paint, 40-55% less time to settle
- Roughly half the memory for the same rows
- About 2.5ร faster sorting at 300k and 1M rows
- Less layout and style recalculation work while scrolling vertically, and about 25% less main-thread work while scrolling horizontally
Where AG Grid came out ahead:
- Less JavaScript work during vertical scrolling at 300k rows (about 18% less scripting, 6% less main-thread work in total)
- Slightly less style recalculation while scrolling horizontally
And where they were even: frame rate and frame pacing while scrolling, jumping across the dataset, and reaching the last of a million rows.
For us, the clear to-do is the scripting cost per scrolled frame when scrolling vertically. 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. An Apple M1 Pro without CPU throttling is fast; mid-range Windows laptops will look different in absolute terms. 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. Both grids have options that could change these results. We deliberately didn't tune either one, because tuning one grid well and the other badly is the easiest way to make a benchmark unfair.
- 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.
- We have an interest. We make Infinite Table. We've tried to design the harness so that it measures both grids the same way, and to report every metric - including the ones where AG Grid is ahead - but you shouldn't have to trust us. That's what the next section is for.
Run it yourself#
The benchmark lives in the Infinite Table repository, under
examples/src/benchmark. You need git, a recent Node.js (we used 22) and about 2.5 GB of disk space; 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#
git clone https://github.com/infinite-table/infinite-react.git
cd infinite-react
# TODO: git checkout <tag or commit used for the numbers in this post>
# three installs: shared tooling, the grid's source, and the examples app
# (which contains the benchmark)
npm ci
npm ci --prefix source
npm ci --prefix examples
cd examples
npx playwright install chromium COPY
Infinite Table is built from the source you checked out, so that commit decides which version you measure. The AG Grid version is the one in
examples/package.json; to measure another one, install it before building: npm install ag-grid-community@<version> ag-grid-react@<version>.2. Run it#
# from the examples folder: production build, then every scenario at 300k and 1M rows
BENCH_HEADLESS=1 npm run benchmark COPY
A full run takes about 25 minutes on the machine we used: 9 scenarios ร 2 sizes ร 2 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:
# mount and string sorting, 300k rows only, 3 runs per grid - reuses the build
BENCH_HEADLESS=1 BENCH_SIZES=300k BENCH_SCENARIOS=mount,sort-string BENCH_RUNS=3 npm run benchmark:run COPY
3. Read the results#
Every run writes two files to
examples/benchmark-results/<run id>/:report.md- one table per scenario, with the median and standard deviation for each grid, the ratio between them, 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
examples/benchmark-results/latest.md.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_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 two grids side by side#
The pages the benchmark measures are ordinary pages you can open and scroll yourself. After a build:
npm run benchmark:serve COPY
Then open localhost:3000/benchmark/infinite-1m and localhost:3000/benchmark/ag-grid-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:
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 AG Grid team: if there's a configuration you'd recommend for a fair comparison, we'll gladly add it, re-run the numbers, and update this post. You can also reach us at admin@infinite-table.com.