Infinite Table vs AG Grid, benchmarked

300,000 and 1,000,000 rows, the same data and the same interactions in both grids, measured in Chrome. Open source and reproducible: run it on your own machine.

Run 2026-11-11-clean on November 11, 2026 · AG Grid Community 36.3.0 · Infinite Table 10.1.4

352 msto show 1M rows, vs 498 ms for AG Grid
90.7 MBof memory for 1M rows, vs 213 MB
2.2–2.5×faster sorting, 300k and 1M rows
60 fpsscrolling 300k rows, for both grids: a tie

Results

Medians of 7 runs per grid and scenario. Where the 95% confidence interval includes "no difference", we call it a tie. Every chart has a table view, and every scenario has a table with all its metrics.

Mount

From setState with all rows already in memory to the first frame that shows the whole viewport correctly, and until the grid stops changing the DOM.

Mount: first rows painted
median of 7 runs · lower is better
300k rows
198 ms198 msAG Grid300k rows41% higher than Infinite Table
140 ms140 msInfinite Table300k rows29% lower than AG Grid
1M rows
498 ms498 msAG Grid1M rows41% higher than Infinite Table
352 ms352 msInfinite Table1M rows29% lower than AG Grid
View as table
Mount: first rows paintedAG GridInfinite Table
300k rows198 ms140 ms
1M rows498 ms352 ms
Mount: until settled
median of 7 runs · lower is better
300k rows
302 ms302 msAG Grid300k rows116% higher than Infinite Table
140 ms140 msInfinite Table300k rows54% lower than AG Grid
1M rows
602 ms602 msAG Grid1M rows71% higher than Infinite Table
352 ms352 msInfinite Table1M rows42% lower than AG Grid
View as table
Mount: until settledAG GridInfinite Table
300k rows302 ms140 ms
1M rows602 ms352 ms
JS heap retained by the grid
after garbage collection · lower is better
300k rows
67.5 MB67.5 MBAG Grid300k rows84% higher than Infinite Table
36.6 MB36.6 MBInfinite Table300k rows46% lower than AG Grid
1M rows
213 MB213 MBAG Grid1M rows135% higher than Infinite Table
90.7 MB90.7 MBInfinite Table1M rows57% lower than AG Grid
View as table
JS heap retained by the gridAG GridInfinite Table
300k rows67.5 MB36.6 MB
1M rows213 MB90.7 MB
Mount, 300k rows: every metric

Mount the grid with all the rows (data already in memory). "Visible" = from setState to the end of the first frame committed with the whole viewport (first/last visible row and column) showing the right content. "Settled" = until the first frame committed after the grid's last DOM mutation. Memory is the JS heap retained by the grid after GC (heap after mount - heap before mount).

MetricAG GridInfinite TableInfinite / AGBetter
Time to first rows paintedlower is better198 ms140 ms0.71×95% CI 0.64–0.78Infinite Table
Time until settledlower is better302 ms140 ms0.46×95% CI 0.43–0.51Infinite Table
Main thread: scriptinglower is better180 ms126 ms0.70×95% CI 0.64–0.80Infinite Table
Main thread: layoutlower is better7.64 ms2.33 ms0.31×95% CI 0.28–0.34Infinite Table
Main thread: style recalclower is better7.76 ms3.82 ms0.49×95% CI 0.46–0.55Infinite Table
Main thread: all taskslower is better205 ms145 ms0.71×95% CI 0.65–0.77Infinite Table
JS heap retained by gridlower is better67.5 MB36.6 MB0.54×95% CI 0.54–0.54Infinite Table
DOM elements in gridlower is better6376090.96×95% CI 0.96–0.96Infinite Table
Mount, 1M rows: every metric

Mount the grid with all the rows (data already in memory). "Visible" = from setState to the end of the first frame committed with the whole viewport (first/last visible row and column) showing the right content. "Settled" = until the first frame committed after the grid's last DOM mutation. Memory is the JS heap retained by the grid after GC (heap after mount - heap before mount).

MetricAG GridInfinite TableInfinite / AGBetter
Time to first rows paintedlower is better498 ms352 ms0.71×95% CI 0.57–0.79Infinite Table
Time until settledlower is better602 ms352 ms0.59×95% CI 0.48–0.65Infinite Table
Main thread: scriptinglower is better480 ms342 ms0.71×95% CI 0.57–0.80Infinite Table
Main thread: layoutlower is better7.13 ms2.13 ms0.30×95% CI 0.28–0.31Infinite Table
Main thread: style recalclower is better7.84 ms3.62 ms0.46×95% CI 0.43–0.50Infinite Table
Main thread: all taskslower is better510 ms355 ms0.70×95% CI 0.57–0.78Infinite Table
JS heap retained by gridlower is better213 MB90.7 MB0.42×95% CI 0.42–0.42Infinite Table
DOM elements in gridlower is better6376090.96×95% CI 0.96–0.96Infinite Table

Sorting

Click a column header, from pointerdown to the first frame with every visible row sorted.

Sort a string column (ascending)
median of 7 runs · lower is better
300k rows
774 ms774 msAG Grid300k rows122% higher than Infinite Table
348 ms348 msInfinite Table300k rows55% lower than AG Grid
1M rows
3,384 ms3,384 msAG Grid1M rows143% higher than Infinite Table
1,395 ms1,395 msInfinite Table1M rows59% lower than AG Grid
View as table
Sort a string column (ascending)AG GridInfinite Table
300k rows774 ms348 ms
1M rows3,384 ms1,395 ms
Sort a number column (ascending)
median of 7 runs · lower is better
300k rows
707 ms707 msAG Grid300k rows131% higher than Infinite Table
306 ms306 msInfinite Table300k rows57% lower than AG Grid
1M rows
3,061 ms3,061 msAG Grid1M rows154% higher than Infinite Table
1,205 ms1,205 msInfinite Table1M rows61% lower than AG Grid
View as table
Sort a number column (ascending)AG GridInfinite Table
300k rows707 ms306 ms
1M rows3,061 ms1,205 ms
Sort a string column, 300k rows: every metric

Click the "code" column header (string column, unique-ish values) - first click sorts ascending (cold), second click descending. Measured from pointerdown to the end of the first frame committed with all visible rows sorted (and each row consistent: its cells belong to the same data row).

MetricAG GridInfinite TableInfinite / AGBetter
Ascending: click to sorted rows paintedlower is better774 ms348 ms0.45×95% CI 0.38–0.46Infinite Table
Ascending: click to settledlower is better774 ms362 ms0.47×95% CI 0.39–0.48Infinite Table
Descending: click to sorted rows paintedlower is better810 ms360 ms0.44×95% CI 0.40–0.49Infinite Table
Descending: click to settledlower is better810 ms360 ms0.44×95% CI 0.40–0.49Infinite Table
Ascending: main thread scriptinglower is better762 ms335 ms0.44×95% CI 0.37–0.45Infinite Table
Descending: main thread scriptinglower is better801 ms347 ms0.43×95% CI 0.39–0.48Infinite Table
Sort a number column, 300k rows: every metric

Click the "salary" column header (number column, unique-ish values) - first click sorts ascending (cold), second click descending. Measured from pointerdown to the end of the first frame committed with all visible rows sorted (and each row consistent: its cells belong to the same data row).

MetricAG GridInfinite TableInfinite / AGBetter
Ascending: click to sorted rows paintedlower is better707 ms306 ms0.43×95% CI 0.38–0.45Infinite Table
Ascending: click to settledlower is better707 ms312 ms0.44×95% CI 0.39–0.48Infinite Table
Descending: click to sorted rows paintedlower is better714 ms314 ms0.44×95% CI 0.37–0.48Infinite Table
Descending: click to settledlower is better714 ms314 ms0.44×95% CI 0.37–0.48Infinite Table
Ascending: main thread scriptinglower is better695 ms295 ms0.42×95% CI 0.37–0.45Infinite Table
Descending: main thread scriptinglower is better705 ms305 ms0.43×95% CI 0.36–0.48Infinite Table
Sort a string column, 1M rows: every metric

Click the "code" column header (string column, unique-ish values) - first click sorts ascending (cold), second click descending. Measured from pointerdown to the end of the first frame committed with all visible rows sorted (and each row consistent: its cells belong to the same data row).

MetricAG GridInfinite TableInfinite / AGBetter
Ascending: click to sorted rows paintedlower is better3,384 ms1,395 ms0.41×95% CI 0.37–0.44Infinite Table
Ascending: click to settledlower is better3,384 ms1,412 ms0.42×95% CI 0.38–0.44Infinite Table
Descending: click to sorted rows paintedlower is better3,279 ms1,380 ms0.42×95% CI 0.38–0.45Infinite Table
Descending: click to settledlower is better3,279 ms1,380 ms0.42×95% CI 0.38–0.45Infinite Table
Ascending: main thread scriptinglower is better3,373 ms1,383 ms0.41×95% CI 0.37–0.43Infinite Table
Descending: main thread scriptinglower is better3,271 ms1,366 ms0.42×95% CI 0.38–0.45Infinite Table
Sort a number column, 1M rows: every metric

Click the "salary" column header (number column, unique-ish values) - first click sorts ascending (cold), second click descending. Measured from pointerdown to the end of the first frame committed with all visible rows sorted (and each row consistent: its cells belong to the same data row).

MetricAG GridInfinite TableInfinite / AGBetter
Ascending: click to sorted rows paintedlower is better3,061 ms1,205 ms0.39×95% CI 0.34–0.47Infinite Table
Ascending: click to settledlower is better3,061 ms1,213 ms0.40×95% CI 0.34–0.47Infinite Table
Descending: click to sorted rows paintedlower is better3,096 ms1,289 ms0.42×95% CI 0.40–0.44Infinite Table
Descending: click to settledlower is better3,096 ms1,289 ms0.42×95% CI 0.40–0.44Infinite Table
Ascending: main thread scriptinglower is better3,049 ms1,193 ms0.39×95% CI 0.34–0.46Infinite Table
Descending: main thread scriptinglower is better3,088 ms1,275 ms0.41×95% CI 0.39–0.44Infinite Table

Jumping through the dataset

Ten programmatic jumps to far-away positions, like dragging the scrollbar thumb.

Jump to rows painted
median of 10 jumps per run · lower is better
300k rowsno significant difference
21 ms21 msAG Grid300k rows23% lower than Infinite Table
27.1 ms27.1 msInfinite Table300k rows29% higher than AG Grid
1M rows
20.9 ms20.9 msAG Grid1M rows11% lower than Infinite Table
23.6 ms23.6 msInfinite Table1M rows13% higher than AG Grid
View as table
Jump to rows paintedAG GridInfinite Table
300k rows (no significant difference)21 ms27.1 ms
1M rows20.9 ms23.6 ms
Jump through the dataset, 300k rows: every metric

Programmatic jumps to 10 far-away scroll positions (like dragging the scrollbar thumb). Measured from setting scrollTop to the end of the first frame committed with the first and last visible rows showing the right content, at the right position.

MetricAG GridInfinite TableInfinite / AGBetter
Jump to rows painted (median of jumps)lower is better21 ms27.1 ms1.29×95% CI 0.90–1.47no significant difference
Jump to rows painted (worst jump)lower is better39.9 ms36.7 ms0.92×95% CI 0.72–1.18no significant difference
Jump to settled (median of jumps)lower is better21 ms27.1 ms1.29×95% CI 0.90–1.47no significant difference
Main thread scripting (all jumps)lower is better133 ms163 ms1.22×95% CI 0.96–1.36no significant difference
Jump through the dataset, 1M rows: every metric

Programmatic jumps to 10 far-away scroll positions (like dragging the scrollbar thumb). Measured from setting scrollTop to the end of the first frame committed with the first and last visible rows showing the right content, at the right position.

MetricAG GridInfinite TableInfinite / AGBetter
Jump to rows painted (median of jumps)lower is better20.9 ms23.6 ms1.13×95% CI 1.01–1.27AG Grid
Jump to rows painted (worst jump)lower is better35.6 ms28.3 ms0.79×95% CI 0.70–0.84Infinite Table
Jump to settled (median of jumps)lower is better20.9 ms23.6 ms1.13×95% CI 1.01–1.27AG Grid
Main thread scripting (all jumps)lower is better127 ms143 ms1.12×95% CI 0.98–1.17no significant difference

Scrolling

A wheel gesture through Chrome's real input pipeline: 24,000 px down at 8,000 px/s, and four horizontal sweeps across all columns at 4,000 px/s. At 300k rows both grids hold 60 fps, with no dropped frames and no long animation frames; at 1M rows both stay at 59-60 fps, with no significant difference. Where they differ is how much main-thread work it takes:

Vertical scrolling, 300k rows: main-thread work
total over the gesture, median of 7 runs · lower is better
scripting
398 ms398 msAG Gridscripting16% lower than Infinite Table
474 ms474 msInfinite Tablescripting19% higher than AG Grid
layout
78.3 ms78.3 msAG Gridlayout46% higher than Infinite Table
53.7 ms53.7 msInfinite Tablelayout31% lower than AG Grid
style recalc
172 ms172 msAG Gridstyle recalc41% higher than Infinite Table
122 ms122 msInfinite Tablestyle recalc29% lower than AG Grid
all tasksno significant difference
1,020 ms1,020 msAG Gridall tasks1% lower than Infinite Table
1,029 ms1,029 msInfinite Tableall tasks1% higher than AG Grid
View as table
Vertical scrolling, 300k rows: main-thread workAG GridInfinite Table
scripting398 ms474 ms
layout78.3 ms53.7 ms
style recalc172 ms122 ms
all tasks (no significant difference)1,020 ms1,029 ms
Horizontal scrolling, 300k rows: main-thread work
total over 4 sweeps, median of 7 runs · lower is better
scripting
171 ms171 msAG Gridscripting58% higher than Infinite Table
108 ms108 msInfinite Tablescripting37% lower than AG Grid
layout
48.8 ms48.8 msAG Gridlayout495% higher than Infinite Table
8.2 ms8.2 msInfinite Tablelayout83% lower than AG Grid
style recalc
30.6 ms30.6 msAG Gridstyle recalc10% lower than Infinite Table
34.1 ms34.1 msInfinite Tablestyle recalc11% higher than AG Grid
all tasks
414 ms414 msAG Gridall tasks31% higher than Infinite Table
317 ms317 msInfinite Tableall tasks23% lower than AG Grid
View as table
Horizontal scrolling, 300k rows: main-thread workAG GridInfinite Table
scripting171 ms108 ms
layout48.8 ms8.2 ms
style recalc30.6 ms34.1 ms
all tasks414 ms317 ms
Scroll vertically, 300k rows: every metric

Wheel-scroll down 24000px at 8000px/s (Input.synthesizeScrollGesture - real input pipeline, same gesture for both grids). Frame outcomes come from Chrome's compositor (PipelineReporter): "partial" = frame shown without the main thread's update, "dropped" = no new frame. rAF frame intervals and Long Animation Frames come from the page.

MetricAG GridInfinite TableInfinite / AGBetter
Frames fully presentedhigher is better52.3%56%1.07×95% CI 0.99–1.14no significant difference
Frames presented without main-thread updatelower is better47.7%44%0.92×95% CI 0.84–1.02no significant difference
Frames droppedlower is better0%0%0%95% CI 0% to 0%no significant difference
Main thread frame rate (rAF)higher is better60 fps59.9 fps1.00×95% CI 0.99–1.00no significant difference
p95 rAF frame intervallower is better17.5 ms17.5 ms1.00×95% CI 0.99–1.01no significant difference
Worst rAF frame intervallower is better17.7 ms17.7 ms1.00×95% CI 1.00–1.90no significant difference
rAF intervals > 25mslower is better0%0%0%95% CI 0% to 0.54%no significant difference
Long animation frames: total blockinglower is better0 ms0 ms0 ms95% CI 0 ms to 0 msno significant difference
Main thread: scriptinglower is better398 ms474 ms1.19×95% CI 1.10–1.32AG Grid
Main thread: layoutlower is better78.3 ms53.7 ms0.68×95% CI 0.60–0.78Infinite Table
Main thread: style recalclower is better172 ms122 ms0.71×95% CI 0.65–0.76Infinite Table
Main thread: all taskslower is better1,020 ms1,029 ms1.01×95% CI 0.89–1.11no significant difference
Scroll horizontally, 300k rows: every metric

Wheel-scroll right/left over all columns, 4 sweeps at 4000px/s (Input.synthesizeScrollGesture - real input pipeline, same gesture for both grids). Frame outcomes come from Chrome's compositor (PipelineReporter): "partial" = frame shown without the main thread's update, "dropped" = no new frame. rAF frame intervals and Long Animation Frames come from the page.

MetricAG GridInfinite TableInfinite / AGBetter
Frames fully presentedhigher is better88.9%95.8%1.08×95% CI 0.94–1.12no significant difference
Frames presented without main-thread updatelower is better11.1%4.23%0.38×95% CI 0.26–1.93no significant difference
Frames droppedlower is better0%0%0%95% CI 0% to 0%no significant difference
Main thread frame rate (rAF)higher is better60 fps60 fps1.00×95% CI 1.00–1.00no significant difference
p95 rAF frame intervallower is better17.3 ms17.2 ms0.99×95% CI 0.98–1.01no significant difference
Worst rAF frame intervallower is better17.7 ms17.6 ms0.99×95% CI 0.99–1.01no significant difference
rAF intervals > 25mslower is better0%0%0%95% CI 0% to 0%no significant difference
Long animation frames: total blockinglower is better0 ms0 ms0 ms95% CI 0 ms to 0 msno significant difference
Main thread: scriptinglower is better171 ms108 ms0.63×95% CI 0.60–0.70Infinite Table
Main thread: layoutlower is better48.8 ms8.2 ms0.17×95% CI 0.16–0.18Infinite Table
Main thread: style recalclower is better30.6 ms34.1 ms1.11×95% CI 1.00–1.17AG Grid
Main thread: all taskslower is better414 ms317 ms0.77×95% CI 0.73–0.80Infinite Table
Scroll vertically, 1M rows: every metric

Wheel-scroll down 24000px at 8000px/s (Input.synthesizeScrollGesture - real input pipeline, same gesture for both grids). Frame outcomes come from Chrome's compositor (PipelineReporter): "partial" = frame shown without the main thread's update, "dropped" = no new frame. rAF frame intervals and Long Animation Frames come from the page.

MetricAG GridInfinite TableInfinite / AGBetter
Frames fully presentedhigher is better52.3%58.5%1.12×95% CI 1.03–1.14Infinite Table
Frames presented without main-thread updatelower is better47.7%41.4%0.87×95% CI 0.86–0.96Infinite Table
Frames droppedlower is better0%0%0%95% CI -0.56% to 0.55%no significant difference
Main thread frame rate (rAF)higher is better59.9 fps59.7 fps1.00×95% CI 0.99–1.02no significant difference
p95 rAF frame intervallower is better17.5 ms17.5 ms1.00×95% CI 1.00–1.02no significant difference
Worst rAF frame intervallower is better17.7 ms33.5 ms1.89×95% CI 0.27–1.92no significant difference
rAF intervals > 25mslower is better0%0.54%+0.54%95% CI -0.55% to 1.09%no significant difference
Long animation frames: total blockinglower is better0 ms0 ms0 ms95% CI 0 ms to 0 msno significant difference
Main thread: scriptinglower is better502 ms593 ms1.18×95% CI 1.11–1.26AG Grid
Main thread: layoutlower is better86.6 ms69.6 ms0.80×95% CI 0.74–0.88Infinite Table
Main thread: style recalclower is better274 ms153 ms0.56×95% CI 0.52–0.59Infinite Table
Main thread: all taskslower is better1,319 ms1,269 ms0.96×95% CI 0.89–1.00Infinite Table
Scroll horizontally, 1M rows: every metric

Wheel-scroll right/left over all columns, 4 sweeps at 4000px/s (Input.synthesizeScrollGesture - real input pipeline, same gesture for both grids). Frame outcomes come from Chrome's compositor (PipelineReporter): "partial" = frame shown without the main thread's update, "dropped" = no new frame. rAF frame intervals and Long Animation Frames come from the page.

MetricAG GridInfinite TableInfinite / AGBetter
Frames fully presentedhigher is better86.3%89%1.03×95% CI 0.94–1.04no significant difference
Frames presented without main-thread updatelower is better13.7%11%0.81×95% CI 0.75–1.64no significant difference
Frames droppedlower is better0%0%0%95% CI 0% to 0%no significant difference
Main thread frame rate (rAF)higher is better60 fps60 fps1.00×95% CI 1.00–1.00no significant difference
p95 rAF frame intervallower is better17.4 ms17.4 ms1.00×95% CI 0.99–1.01no significant difference
Worst rAF frame intervallower is better17.7 ms17.6 ms0.99×95% CI 0.99–1.01no significant difference
rAF intervals > 25mslower is better0%0%0%95% CI 0% to 0%no significant difference
Long animation frames: total blockinglower is better0 ms0 ms0 ms95% CI 0 ms to 0 msno significant difference
Main thread: scriptinglower is better165 ms111 ms0.67×95% CI 0.61–0.75Infinite Table
Main thread: layoutlower is better51.5 ms8.79 ms0.17×95% CI 0.16–0.19Infinite Table
Main thread: style recalclower is better30.3 ms35.4 ms1.17×95% CI 1.00–1.30no significant difference
Main thread: all taskslower is better402 ms353 ms0.88×95% CI 0.71–0.94Infinite Table

What you see during a very fast scroll

When you flick the wheel faster than any grid can re-render, a grid has to show something for the rows it hasn't caught up with. AG Grid shows empty space until the right content is rendered. Infinite Table moves the existing rows into place immediately and updates their content on the next render, so strong inertial scrolling never reveals blank areas, but for a frame or two some rows can show the content they held before. For horizontal scrolling it waits for the render.

The benchmark measures both effects during a 20,000 px/s flick. Those numbers are in the full report.

Method

Setup

  • The same deterministic data in both grids: 20 columns, at 300,000 and 1,000,000 rows, with a checksum verified before every run.
  • Same row height, header height and column widths. Otherwise each grid runs on its defaults. AG Grid uses the Quartz theme, animateRows={false} and a getRowId.
  • Production builds only.
  • 7 measured runs per grid and scenario, plus 1 discarded warmup, interleaved A/B, a fresh browser context for every run.

How we measure

  • Latency comes from Chrome performance traces, as in js-framework-benchmark: from the input event to the end of the first frame where the DOM shows the expected rows and columns.
  • Scrolling uses Input.synthesizeScrollGesture, so both grids get the same real wheel input. Frame outcomes come from Chrome's compositor.
  • Medians, with a seeded 95% bootstrap confidence interval of the ratio between the grids. We only call a winner when the interval excludes "no difference".

Machine and browser

MachineApple M1 Pro × 10, Darwin 24.1.0 arm64
BrowserChrome 136.0.7103.25, headless, sandboxed, display scale 1
CPU throttling1× (none)
Viewport1400×1000
Runs7 measured + 1 warmup per grid and scenario
VersionsAG Grid Community 36.3.0, Infinite Table 10.1.4 (built from source)
Run2026-11-11-clean, November 11, 2026

Limitations

  • One machine, one browser. A fast laptop without CPU throttling. Compare the ratios between the grids, not the absolute numbers.
  • Headless Chrome. It runs the same rendering code as headed Chrome, but frame pacing can differ with a real display.
  • Defaults only. Both grids have options that could change these results. We didn't tune either.
  • One dataset shape. Plain text and numbers: no custom cell renderers, grouping, filtering or live updates.
  • We have an interest. We make Infinite Table. That is why everything here is open, and why you can run it yourself.

Run it yourself

The benchmark lives in examples/src/benchmark in the Infinite Table repository. You need git, Node.js (we used 22) and about 2.5 GB of disk space, on macOS or Linux. Playwright downloads its own Chromium.

git clone https://github.com/infinite-table/infinite-react.git
cd infinite-react

# three installs: shared tooling, the grid's source, the examples app
npm ci
npm ci --prefix source
npm ci --prefix examples

cd examples
npx playwright install chromium

# production build, then every scenario at 300k and 1M rows (about 25 minutes)
BENCH_HEADLESS=1 npm run benchmark

# or a one-minute taste: mount and string sorting, 300k rows, 3 runs
BENCH_HEADLESS=1 BENCH_SIZES=300k BENCH_SCENARIOS=mount,sort-string BENCH_RUNS=3 npm run benchmark:run

Each run writes report.md (medians, ratios, confidence intervals) and results.json (every raw sample) to examples/benchmark-results/<run id>/. The README documents every option and scenario.

Help us make it fairer

If you know a setting that makes either grid faster, or a scenario we should add, please open an issue or a pull request. We want the benchmark to be fair to both grids.