A data grid is where users of an enterprise web app spend most of their day. Support consoles, logistics manifests, financial reports: they all come down to a table, and people now expect that table to maintain responsiveness under high workloads. Many JS grid components on the market perform well in a demo with a thousand rows, but what happens when the number of rows increases tens or hundreds of times? That's what really interests developers who need to add the data grid to a modern enterprise-grade app.
Disclosure: I’m a product developer at DHTMLX, so the question isn’t academic for me.
To get an answer worth publishing, I benchmarked five JavaScript Grid components using the same test suite: DHTMLX Grid, AG Grid, Bryntum Grid, DevExtreme DataGrid, and Kendo UI Grid. The benchmark uses the same seeded data, row height, and scroll workload across five dataset sizes (from 10,000 to 800,000 rows). That gives 40 configurations per grid and 200 measurement points across all five grids.
The complete JavaScript Grid performance benchmark repository contains the test suite and configurations needed to reproduce these measurements on your own machine.
What I Measured
I focused on three metrics that cover the main areas where users usually notice performance issues in grid tables.
Building the grid. The time from initializing the grid until the viewport contains the expected number of rows. This measurement ignores grids’ “ready” signals, because each component defines this point differently.
Jump to depth. The time between programmatically jumping to 90% of the dataset and the moment the viewport displays the rows at that position. This shows how quickly grids can catch up when the user decides to move from the beginning of the table to a point close to its end.
Scrolling. A single FPS figure isn't enough, so I collected a series of frame times while the grid’s scroll container was driven programmatically over 50,000 px. Every frame is recorded, allowing us to calculate the median, 95th percentile, and worst frame time. FPS is reported as additional context.
The benchmark uses the same setup for all five grid components: one seeded dataset, a fixed row height of 40 px, and a 1920×1080 viewport. The fixed 40 px row height keeps the scrolling workload comparable across grids. Without it, the same scroll distance could cover a different number of rows in each grid. The rendered height is checked during each run and reports an error if grids do not comply with the 40 px setting.
Now, I want to take a little detour and dwell a bit on frames per second (FPS), the metric everyone publishes, and discuss why it tells surprisingly little about how JS grid components actually behave while scrolling.
Why FPS Numbers Can Be Confusing
The FPS metric reduces an entire sequence of frame times to a single number. That makes it easy to compare at a glance, but it doesn’t show how those frames are distributed.
Let’s say, for instance, that one grid provides mostly smooth frames with occasional long pauses, while another grid may be slower on every frame. These two grid components can have similar FPS numbers, but these are two different behavior patterns. FPS demonstrates how many frames are produced as a whole, but says nothing about consistency or how long the slowest frame takes. As a result, FPS can make occasional stutters and continuous heavy rendering look deceptively similar.
There is another noteworthy factor related to the 60 Hz display used for testing. At this refresh rate, frames that finish within 16.7 ms have the same FPS rate. It means that two grids can show the same FPS, but at the same time, have different frame times.
That is why I took a different approach to scrolling measurement. Instead of relying on FPS, I recorded the frame times themselves and used their distribution to evaluate scrolling performance.
Why FPS Hides Performance Differences: One Example
A single benchmark configuration is enough to show why frame-time distribution provides more information than FPS alone.
The example below uses 100,000 rows, 20 columns, and real HTML content rendered inside cells. It compares the same scrolling scenario using four frame-related metrics: median frame time, 95th percentile (P95), worst observed frame, and the resulting FPS value.
| Grid | Median frame, ms | P95 frame, ms | Worst frame, ms | FPS |
|---|---|---|---|---|
| AG Grid | 16.7 | 26.19 | 69 | 57 |
| Bryntum | 16.6 | 23.1 | 38.5 | 60 |
| DevExtreme | 18.7 | 32.65 | 72.8 | 51 |
| DHTMLX | 16.6 | 21.23 | 41.6 | 60 |
| Kendo | 16.9 | 95.26 | 111.5 | 35 |
The median stays within a narrow range of 16.6-18.7 ms across all five grids, so it does not show any significant differences in scrolling behavior. The P95 parameter is more revealing: DHTMLX records the lowest value at 21.23 ms, followed by Bryntum at 23.1 ms, AG Grid at 26.19 ms, DevExtreme at 32.65 ms, and Kendo at 95.26. In practical terms, these values mean that 95% of the recorded frames took no more than the corresponding P95 value to render. A lower P95 indicates that a larger share of the scrolling sequence stays within a shorter frame time, while a higher value points to a greater number of slow frames.
The worst-frame values identify the largest single spikes observed during the measurement. Because this metric represents only one frame, it should not be treated as a characteristic of common scrolling performance, but it helps reveal frame-time peaks that median numbers do not show.
The FPS results show the need to consider underlying frame-time data to evaluate scrolling performance properly. DHTMLX and Bryntum both report 60 FPS, followed by AG-Grid with 57 FPS, yet their P95 worst-frame values show more noticeable differences. That is why looking at the full set of frame-time metrics gives a more objective perspective on scrolling performance.
Important Benchmark Details
The benchmark is designed to be transparent and fully reproducible, with the same methodology applied to all five grids. It should help exclude potential biases. A seed drives the row generator, so each testing scenario uses the same generated data. All grid-specific settings added in the benchmark are listed in the project repository. No other performance tuning is applied beyond those documented settings.
Each test is performed five times after one discarded warm-up pass. The browser window remains visible during testing. Each result records the grid version, machine, browser, and display refresh rate used for the run.
All measurements are taken on an eight-core Ryzen 7 4700U.
Here are the exact packages and grid versions used for testing:
| Grid | npm package | Build measured |
|---|---|---|
| AG Grid | ag-grid-community | 36.0.2 |
| Bryntum Grid | @bryntum/grid-trial | 7.3.4 |
| DevExtreme DataGrid | devextreme | 26.1.3 |
| DHTMLX Grid | @dhx/trial-grid | 9.3.8 |
| Kendo UI Grid | @progress/kendo-ui | 2026.2.520 |
Note: AG Grid is measured here as the free ag-grid-community package, the other four as commercial trials.
This benchmark focuses on rendering, scrolling, and navigation. Operations like sorting, filtering, and streaming updates are outside the scope because they have many possible implementations and usage patterns, while the benchmark needs to choose one representative workflow to make comparisons meaningful. Instead of horizontal scrolling, the benchmark measures column count as a clearer performance indicator.
How Different Workloads Affect Grid Performance
The benchmark uses four workload variables: row count, column count, cell content, and pinned columns. Each variable is tested independently while the other inputs remain fixed, making it easier to isolate its impact on the measured grid performance.
Row Count
To see how initial rendering changes as the dataset grows, the chart below shows build times with ten columns and rows ranging from 10,000 to 800,000.
| Grid | 10k | 50k | 100k | 200k | 800k |
|---|---|---|---|---|---|
| AG Grid | 148.6 | 199.4 | 255.8 | 386.1 | 1149.4 |
| Bryntum | 268.6 | 532.3 | 838.8 | 1503.1 | 5330.2 |
| DevExtreme | 180.5 | 182.8 | 178.1 | 179.6 | 186.1 |
| DHTMLX | 55 | 65.5 | 73.8 | 93.3 | 242.8 |
| Kendo | 93.1 | 93.5 | 91.1 | 96.5 | 115.2 |
Build time in milliseconds, ten template columns. Bold marks the fastest in each column.
Ranking the values is less useful than putting them on a scale that reflects how people typically perceive delays: sub-100 ms responses generally feel immediate, around 1 second is clearly noticeable, and delays of several seconds can make an application feel unresponsive. Against that scale, DHTMLX remains below 100 ms through 200,000 rows and reaches 242.8 ms at 800,000 rows. Kendo stays close to the 100 ms range across all five sizes, while DevExtreme stays around 180 ms. AG Grid stays below 1 second until the largest dataset, where build time reaches 1149.4 ms. Bryntum crosses the 1-second mark at 200,000 rows and reaches 5330.2 ms at 800,000 rows.
Column Count
The next variable is the number of columns configured in the grid. The chart below shows how P95 frame time changes as the column count increases from 10 to 40, with the dataset fixed at 100,000 rows. The scrolling test remains vertical throughout, so the results measure the rendering cost of a wider grid rather than horizontal scrolling performance.
| Grid | 10 columns | 20 columns | 40 columns |
|---|---|---|---|
| AG Grid | 18.93 | 26.19 | 27.1 |
| Bryntum | 19.4 | 23.1 | 29.9 |
| DevExtreme | 26.34 | 32.65 | 30.15 |
| DHTMLX | 17.4 | 21.23 | 21.07 |
| Kendo | 55.6 | 95.26 | 173.69 |
P95 frame time in milliseconds, 100 000 rows. Bold marks the lowest in each column.
DHTMLX records the lowest P95 at all three column counts, with only a small change as the number of columns increases. For four other tested grid components, moving from 10 to 40 columns adds between 3.67 and 10.5 ms to P95. Kendo shows a much larger increase in this configuration, from 55.6 to 173.69 ms, or about 3.1 x times.
Cell Templates
Cell content can also affect scrolling performance, so this test compares the same dataset in two versions: one with rich HTML content such as status pills, star ratings, colored badges, and progress bars, and one with the same fields rendered as plain text. The same markup is generated for each grid, keeping the cell content consistent across the comparison.
The table below shows the result of subtracting plain-text P95 from template P95, in milliseconds, at 100,000 rows. A positive number means that rich content increased P95. A negative number means that the plain-text run was marginally slower.
| Grid | 10 columns, ms | 20 columns, ms | 40 columns, ms |
|---|---|---|---|
| AG Grid | +1.33 | +2.59 | +3.51 |
| Bryntum | +0.7 | +2.1 | +7.3 |
| DevExtreme | −0.16 | +6.43 | +3.52 |
| DHTMLX | +0.2 | +3.6 | +3.77 |
| Kendo | +7.12 | +22.16 | +49.89 |
Template P95 minus plain-text P95, in milliseconds, at 100 000 rows. Differences of about 1 ms in either direction are treated as measurement noise.
For four of the five grids, rich cell content adds only a few milliseconds to P95 in these configurations. Only Kendo shows a much larger increase as the column count grows, reaching 49.89 ms at 40 columns.
The broader takeaway is that rich cell content does not automatically result in a substantial scrolling penalty. In these configurations, the measured overhead is modest for four grids, while Kendo shows a more noticeable sensitivity to richer cell templates.
Pinned Columns
Next, I measure the impact of pinned columns on scrolling performance. For this purpose, the benchmark compares each configuration with three columns pinned against the same configuration with all columns scrolling normally.
P95 with three columns pinned, minus P95 for the same scenario without pinning, in milliseconds:
| Grid | 10 col @100k, ms | 40 col @100k, ms | 10 col @800k, ms | 40 col @800k, ms |
|---|---|---|---|---|
| AG Grid | +0.67 | −0.31 | +81.7 | +172.56 |
| Bryntum | −0.5 | +3.2 | −0.3 | −0.9 |
| DevExtreme | +3.36 | +6.91 | +4.08 | +6.52 |
| DHTMLX | −0.2 | +0.76 | −0.1 | +1.15 |
| Kendo | +1.75 | +4.92 | +1.3 | +7.57 |
Bold marks the two figures that fall outside measurement noise.
For Bryntum and DHTMLX, adding three pinned columns produces no meaningful increase in P95 in the tested configurations. DevExtreme and Kendo show a small increase, with P95 rising by a few milliseconds. AG Grid is different: the effect is negligible at 100,000 rows but becomes substantial at 800,000 rows, where pinning increases P95 by 81.7 ms with 10 columns and 172.56 ms with 40 columns.
The practical consequence is about testing rather than choosing: a pinned-column layout that measures clean at 100 000 rows is not evidence that it measures clean at 800 000.
Memory Usage
“How much memory does this grid use?” can mean two different things. For page-level budgeting, the relevant figure is the total heap occupied after the grid has been constructed, including the library, dataset, and grid. For comparing the grid instances themselves, the benchmark measures how much additional heap the grid uses after the library and dataset have been loaded.
That is why the benchmark reports both figures. A forced garbage collection is performed before each measurement to remove objects that are no longer in use.
| Grid | Page total, MB | Grid only, MB |
|---|---|---|
| AG Grid | 320.12 | 178.78 |
| Bryntum | 905.62 | 761.66 |
| DevExtreme | 164.03 | 13.85 |
| DHTMLX | 170.97 | 30.58 |
| Kendo | 162.95 | 15.14 |
800 000 rows, 40 template columns. Bold marks the lowest in each column.
The two measurements answer different questions, so a grid that has a lower page total does not necessarily have a lower grid-only memory usage. That is why both results are reported. At 800,000 rows and 40 template columns, page total ranges from 162.95 MB for Kendo to 905.62 MB for Bryntum, while grid-only memory ranges from 13.85 MB for DevExtreme to 761.66 MB for Bryntum. DHTMLX measures 170.97 MB for page total and 30.58 MB for grid-only, keeping memory usage relatively low in both cases.
The difference between Page total and Grid only represents memory that was already present before the grid was constructed, including the loaded library and dataset. Looking at how this difference changes as the number of columns increases gives an additional insight: when moving from 10 to 40 columns, the gap grows by about 1.14–1.15 MB at 10,000 rows and 11.44–11.46 MB at 100,000 rows across all five grids. Because the same dataset is used throughout the benchmark, the matching increase suggests that much of this additional memory comes from the shared data rather than from the grid implementation.
The remaining variation can be used to estimate the library-related part of this memory. Across the tested grids, that estimate ranges from about 2.72 MB to 12.51 MB. These figures represent an inferred memory contribution from the loaded libraries, not their package size on disk.
One more set of figures is useful for sizing a page budget from a row count. Every extra thousand rows adds this much to page total:
| Grid | per 1000 rows |
|---|---|
| AG Grid | 279 KB |
| Bryntum | 307 KB |
| DevExtreme | 70 KB |
| DHTMLX | 97 KB |
| Kendo | 72 KB |
Page total per 1000 rows, ten template columns. Bold marks the lowest. At 40 columns, the same slopes run from 187 to 1147 KB.
At ten template columns, DevExtreme, Kendo, and DHTMLX add between 70 and 97 KB per 1,000 rows, while AG Grid and Bryntum add substantially more. The same scaling pattern becomes more pronounced with 40 columns, where the measured increase ranges from 187 to 1,147 KB per 1,000 rows.
The workload results give a more complete picture of how the five grids behave under different conditions. The next section brings these findings together for each grid.
Performance Profiles of Tested Grid Tables
The analysis of test results shows that five grids respond differently as the workload changes. The table below summarizes performance patterns observed for each grid.
| Grid component | Performance patterns in the benchmark |
|---|---|
| DHTMLX Grid | DHTMLX Grid shows a particularly stable performance profile across the benchmark matrix, with median frame time staying at 16.6-16.7 ms and P95 at 17.1-23.05 ms across all 40 scenarios. No recorded frame exceeds 45 ms. Build time rises from 55 ms to 242.8 ms and jump time stays at 32.8-49.1 ms as the workload grows. Changes in columns, cell content, and pinned columns cause no pronounced deterioration. |
| AG Grid | Performance remains relatively stable through 200,000 rows, while the 800,000-row scenarios demonstrate substantially higher frame times. Across the 800,000-row configurations, the median reaches 31.9 ms. The most challenging scenario, with 40 columns and three pinned columns, takes 95.5 ms median and 228 ms P95. In that configuration, AG Grid requires 30.8 seconds to cover the fixed 50,000 px scroll distance, compared to 3.7 seconds for DHTMLX. At smaller dataset sizes, pinned columns show little measurable overhead, but the impact becomes much larger at 800,000 rows. The measured P95 increase is 81.7 ms with 10 columns and 172.56 ms with 40 columns. |
| Bryntum Grid | The main cost appears during initial rendering with larger datasets. The build time reaches 5,330.2 ms with 800,000 rows and 10 columns and 12,292.1 ms with 40 columns, while grid-only heap comes to 761.66 MB, the highest figure in the benchmark. When the grid is ready for operation, frame-time results become more stable, with P95 remaining between 18.4 and 33.32 ms across the tested configurations. |
| DevExtreme DataGrid | DevExtreme's build time changes very little with row count. It ranges from 180.5 ms at 10,000 rows to 186.1 ms at 800,000, even though the dataset is becoming 80 times larger. However, the picture changes when looking into the scrolling details. P95 ranges from 25.9 to 40.29 ms across the matrix, and the fixed 50,000 px scroll takes 3,279.9 ms at 100,000 rows. Pinned columns add between 3.36 and 6.91 ms to P95 across the tested sizes. |
| Kendo UI Grid | Kendo’s P95 results are particularly sensitive to column count in tested scenarios. At 100,000 rows, it increases from 55.6 ms with 10 columns to 95.26 ms with 20 and 173.69 ms with 40. By comparison, 40-column P95 changes from 168 ms at 10,000 rows to 196.33 ms at 800,000. Two additional performance-sensitive areas are rich cell content, which adds up to 49.89 ms to P95 at 40 columns, and navigation jumps (jump time), reaching 762.9 ms at 800,000 rows and 40 columns with three pinned columns. |
Taken together, the results show why a single dataset size or column configuration can give an incomplete picture of grid performance. Different workload dimensions produce the largest changes in different parts of the matrix, making it important to test the combination of rows, columns, cell complexity, and pinned columns that matches the intended application.
Run Benchmark Tests Yourself
The full benchmark can be reproduced from the project repository with the following commands:
npm ci
npm run vendor
npx playwright install chromium
npm run bench -- --set=article --sizes=10000,50000,100000,200000,800000 --runs=5 --warmup=1 --order=random --cleanup
npm run report
The complete benchmark suite, including the test methodology, configurations, and reproducible results, is available in the GitHub repository.
The benchmark runs with a visible browser window, so keep the display active throughout the test and disable any screensaver or display-sleep behavior that could interrupt it. The benchmark may take several hours to complete depending on the machine. If the run is stopped early, results from completed configurations are preserved and can still be included in the report.
Wrapping Up
The benchmark shows why a single FPS number or dataset size is not enough to describe grid performance. Median, P95, and worst-frame results reveal differences that average FPS can hide, while build time, Jump Time, and memory usage respond differently as rows, columns, cell content, and pinned columns change.
DHTMLX maintains a particularly stable profile, with median frame times of 16.6-16.7 ms and P95 values of 17.1-23.05 ms across all 40 configurations. The other grids show more pronounced sensitivity to specific workload variables, with the largest changes appearing in different parts of the benchmark.
The numbers above provide a useful reference, but the run that settles the question for your application is the one on your hardware, with your data and your column set.


Top comments (0)