Benchmarks
Compare gp-grid scroll, render, sort/filter and memory performance against popular data grid libraries on a 1,000,000-row dataset.
These benchmarks compare gp-grid against other popular data grid libraries on a single 1,000,000-row dataset, using an identical set of columns and the same seeded data for every grid. Each metric below is the median across repeated iterations. The exact machine, runtime and library versions used for this run are listed with the results so the numbers are reproducible.
Methodology
- Same data, same columns. Every grid renders the same deterministically generated rows and columns; only the grid under test changes.
- Windowed virtualization. All grids keep just the visible row window in the DOM. gp-grid caps its scroll container at 10,000,000px because browsers mis-render taller elements; above ~312,000 rows (at a 32px row height) the natural content height exceeds that cap, so gp-grid compresses the scroll space (a scroll ratio below 1) and maps the scrollbar position back to logical row coordinates. At 1,000,000 rows this compression is active. Grids that instead render a full-height spacer (row count ร row height) rely on the browser's own maximum element-height limits.
- Identical wheel input. The measured scroll sends the same synthetic mouse-wheel events to every grid. Each grid's scroll and virtualization model translates that input into a different travelled distance (custom scrollbars and dampened virtual-scroll wheel handling rescale the deltas), so instead of forcing an equal distance through grid-specific APIs, the tables report Rows Traversed โ how many rows each grid actually covered under the same input.
- Mode column. Each table lists how a grid is wired up.
native-gridis a shipped grid component;headless-table-virtualizersupplies only the data/virtualization model while the benchmark authors the DOM;app-side-virtual-datafeeds a virtual data source from the benchmark. The Notes section below the results spells out the per-grid details.
Metrics Explained
| Metric | Better | Description |
|---|---|---|
| Avg FPS | Higher | Average frames per second during scroll (frames รท elapsed time) |
| P05 FPS | Higher | 5th-percentile FPS โ the slow end of the distribution |
| P95 Frame Time | Lower | 95th-percentile frame duration; lower means fewer long frames |
| Frame Drops | Lower | Number of frames that took longer than 25ms |
| Rows Traversed | โ | Rows covered during the measured scroll (logical scroll delta รท row height) under the identical wheel input |
| Scroll Delta | โ | Logical (content) pixels travelled during the measured scroll |
| Scroll px/s | โ | Logical pixels travelled per second during the measured scroll |
| FCP | Lower | Time to first contentful paint |
| Full Render | Lower | Time until the grid is fully rendered and interactive |
| LCP | Lower | Largest Contentful Paint |
| TBT | Lower | Total Blocking Time |
| After Load | Lower | Heap size after the data is loaded |
| Peak | Lower | Maximum heap size observed during the run |
| Growth / 1K | Lower | Heap increase per 1,000 rows |
| Retained | Lower | Heap not released after clearing the data |
| Minified | Lower | Minified production JS + CSS of the grid's packages, bundled with Vite (React/React DOM excluded) |
| Gzip | Lower | The same bundle after gzip compression |
Run Details
- Run: 2026-07-16T10-29-40-792Z
- Date: 2026-07-16T10:29:40.796Z
- Machine: Intel(R) Core(TM) Ultra 7 258V
- Environment: win32 10.0.26200, 8 logical CPUs, 32244 MB RAM
- Runtime: Node v24.14.0, Chrome 150.0.7871.116
- Config: 10 iterations, 1 Playwright worker, retries 0, overscan 10 rows, 32px row height, 1280x720, headless true
- Scope: DOM-rendering grids. Canvas-rendering grids are excluded: their painting model is not directly comparable.
Library Versions
| Grid | Package | Version |
|---|---|---|
| gp-grid | @gp-grid/react | 0.19.0 |
| ag-grid | ag-grid-community | 35.3.1 |
| ag-grid | ag-grid-react | 35.3.1 |
| tanstack-table | @tanstack/react-table | 9.0.0-beta.16 |
| tanstack-table | @tanstack/react-virtual | 3.14.3 |
| handsontable | handsontable | 17.1.0 |
| smart-grid | smart-webcomponents-react | 26.0.0 |
Package Bundle Sizes
Production ESM JavaScript and CSS bundles of the exact grid-library imports used by each benchmark, minified by Vite and measured with gzip. React and React DOM peer dependencies are excluded; bundled transitive dependencies and imported CSS are included, while other emitted assets are excluded. Every package is sized at the published npm version shown in its Packages cell.
| Grid | Packages | Minified | Gzip |
|---|---|---|---|
| gp-grid | @gp-grid/react@0.19.0 | 170.7 kB | 41.5 kB |
| ag-grid | ag-grid-community@35.3.1, ag-grid-react@35.3.1 | 1098.7 kB | 306.7 kB |
| tanstack-table | @tanstack/react-table@9.0.0-beta.16, @tanstack/react-virtual@3.14.3 | 67.1 kB | 19.2 kB |
| handsontable | handsontable@17.1.0 | 1521.2 kB | 350.1 kB |
| smart-grid | smart-webcomponents-react@26.0.0 | 4082.6 kB | 779.7 kB |
Scroll Performance
Every grid receives the identical mouse-wheel input. Each grid's scroll and virtualization model translates that input into a different distance (custom scrollbars and dampened virtual-scroll wheel handling rescale the deltas), so the Rows Traversed column shows how many rows each grid actually covered during the measured pass.
| Grid | Mode | Rows | Avg FPS | P05 FPS | P95 Frame Time | Frame Drops | Rows Traversed | Scroll Delta | Scroll px/s |
|---|---|---|---|---|---|---|---|---|---|
| gp-grid | native-grid | 1M | 56.2 | 54.3 | 18.4ms | 0 | 501 | 16026px | 934 |
| AG Grid | native-grid | 1M | 41.85 | 44.349999999999994 | 27.15ms | 49 | 1563 | 50000px | 2184.5 |
| Handsontable | native-grid | 1M | 26.55 | 4 | 248.95ms | 69.5 | 1565 | 50080px | 1334 |
| Smart.Grid | app-side-virtual-data | 1M | 56.6 | 54.9 | 18.2ms | 2 | 159 | 5087px | 290.5 |
| TanStack Table | headless-table-virtualizer | 1M | 53.9 | 54.3 | 18.4ms | 2 | 1563 | 50000px | 4088.5 |
Initial Render
| Grid | Mode | Rows | FCP | Full Render | LCP | TBT |
|---|---|---|---|---|---|---|
| gp-grid | native-grid | 1M | 1334ms | 1493.5ms | 1474ms | 1297ms |
| AG Grid | native-grid | 1M | 1348ms | 2530ms | 2430ms | 2153.5ms |
| Handsontable | native-grid | 1M | 388ms | 10202ms | 950ms | 9708.5ms |
| Smart.Grid | app-side-virtual-data | 1M | 172ms | 1359ms | 0ms | 921.5ms |
| TanStack Table | headless-table-virtualizer | 1M | 600ms | 2171.5ms | 1736ms | 1875.5ms |
Sort/Filter Performance
| Grid | Mode | Rows | Sort Asc | Sort Desc | Multi Sort | Text Filter | Number Filter |
|---|---|---|---|---|---|---|---|
| gp-grid | native-grid | 1M | 652ms | 571.5ms | 1219.5ms | 188ms | 129.5ms |
| AG Grid | native-grid | 1M | 2104ms | 1620.5ms | 3044.5ms | 327ms | 281.5ms |
| Handsontable | native-grid | 1M | 33801.5ms | 33571ms | 45970.5ms | 2216.5ms | 1287ms |
| Smart.Grid | app-side-virtual-data | 1M | 1018ms | 523ms | 976.5ms | 191ms | 175.5ms |
| TanStack Table | headless-table-virtualizer | 1M | 1193ms | 1073.5ms | 2530.5ms | 224ms | 243.5ms |
Memory Usage
| Grid | Mode | Rows | After Load | Peak | After Scroll | Growth / 1K | Retained |
|---|---|---|---|---|---|---|---|
| gp-grid | native-grid | 1M | 254.73MB | 267.82MB | 255.32MB | 0.25MB | 0.81MB |
| AG Grid | native-grid | 1M | 461.94MB | 610.68MB | 463.36MB | 0.46MB | 1.97MB |
| Handsontable | native-grid | 1M | 941.42MB | 945.48MB | 942.32MB | 0.94MB | 1.31MB |
| Smart.Grid | app-side-virtual-data | 1M | 244.89MB | 247.82MB | 245.36MB | 0.24MB | 239.94MB |
| TanStack Table | headless-table-virtualizer | 1M | 457.84MB | 481.44MB | 461.75MB | 0.46MB | 445.34MB |
Notes
- gp-grid: Above ~312,000 rows gp-grid caps its DOM scroll container at 10,000,000px, compresses the scroll space, and dampens wheel deltas before applying them, so the shared wheel input traverses fewer rows than on the natively-scrolling grids (see Rows Traversed). Below that threshold it scrolls natively via the wheel like the other grids. Its scroll delta is reported in logical (content) pixels.
- Smart.Grid: Uses Smart.Grid virtualDataSource; sort/filter processing is performed by the benchmark adapter over the full in-memory dataset, then Smart.Grid renders the requested virtual window. Smart.Grid exposes no configurable row overscan, so the shared overscan setting does not apply to it. Its custom scrollbar rescales mouse-wheel input to fixed line steps rather than the event's delta, so under the shared wheel input it traverses a different number of rows than the natively-scrolling grids (see Rows Traversed).
- TanStack Table: TanStack Table is headless: it supplies the sort/filter/row models and, with @tanstack/react-virtual, the row virtualization, but the row and cell DOM is authored by this benchmark. Its render and scroll numbers therefore reflect the benchmark's own markup, not a shipped grid component.
Reproduce
Clone gp-grid, then from the benchmarks/ directory:
pnpm install
pnpm benchOptionally override the defaults with environment variables before pnpm bench:
BENCH_ROW_COUNTSโ comma-separated row counts (default1000000)BENCH_ITERATIONSโ iterations per grid (default5)BENCH_RUN_IDโ a label for the run's results directory
Why GP-Grid
Understand why gp-grid is built for raw speed, simplicity, and a no-feature-gating philosophy that keeps every capability available to every developer.
Onboard your Agent ๐ง
Install the official gp-grid agent skill for your coding agent that integrates the grid into a React, Vue, or Angular app without paraphrasing the docs.