Benchmarks

How long the DataGrid engine takes to sort, filter and search a million rows. Measured on a four-core laptop with a script you can download and run against the same npm package.

Measured September 17, 2026 · @kanunilabs/datagrid-core 1.2.0 · Node 24.14.1 · Intel Core i7-11390H (4 cores, laptop), 16 GB RAM, Windows 11

Sort by a number column
101 ms
Filter by a value
18 ms
Filter, then sort
40 ms

1,000,000 rows × 9 columns, columnar path, median of 3 runs.

1,000,000 rows × 9 columns

Each query ran twice over the same data. The row-based path is plain JavaScript over the row objects. The columnar path first turns each column into a typed array, and it is the exact function the grid's Web Worker runs. The grid switches to it above 20,000 rows, so at this size the gold dots are what a user waits for.

The text sort needs the most explaining. A comparison sort of a million strings calls the collator about 20 million times, which is why the row-based path takes 2.2 s. The columnar path ranks each distinct string with the collator once, keeps those ranks, and from then on sorts integers in 90 ms. But ranking is itself a sort, and in this dataset the product column has a million distinct values. So the first sort on that column took 1.8 s, not much better than the row-based path. A column with a handful of distinct values, like a region or a status, barely notices the first sort.

Search works the same way: the first search lower-cases every distinct value in the text and date columns (413 ms here), and later searches reuse that (65 ms). Both caches last until the data changes.

Columnar (the function the Web Worker runs)Row-based (plain JavaScript over the row objects)Log scale · shorter is faster
10 ms30 ms100 ms300 ms1 s3 sSort, number column: columnar 101 ms, row-based 226 msSort, number columnSort, text column: columnar 90 ms, row-based 2.2 sSort, text columnSort, date column: columnar 92 ms, row-based 351 msSort, date columnSort, two columns: columnar 208 ms, row-based 433 msSort, two columnsFilter, equality: columnar 18 ms, row-based 159 msFilter, equalityFilter, 3 clauses (AND): columnar 60 ms, row-based 206 msFilter, 3 clauses (AND)Search, all columns: columnar 65 ms, row-based 737 msSearch, all columnsFilter + sort: columnar 40 ms, row-based 205 msFilter + sort
1,000,000 rows. Row-based and columnar: median of 3 runs, lowest and highest run below. Columnar first query: the first timing of each run, before per-column caches exist. Ratio: row-based median divided by columnar median.
OperationRow-basedColumnarColumnar, first queryRatio
Sort, number column
226 ms
208 ms309 ms
101 ms
92 ms101 ms
101 ms
68 ms130 ms
2.2×
Sort, text column
2.2 s
1.9 s2.3 s
90 ms
80 ms106 ms
1.8 s
1.6 s1.9 s
24.2×
Sort, date column
351 ms
344 ms355 ms
92 ms
86 ms96 ms
96 ms
69 ms102 ms
3.8×
Sort, two columns
433 ms
433 ms433 ms
208 ms
202 ms218 ms
245 ms
237 ms248 ms
2.1×
Filter, equality
159 ms
159 ms186 ms
18 ms
17 ms19 ms
22 ms
22 ms23 ms
8.9×
Filter, 3 clauses (AND)
206 ms
188 ms267 ms
60 ms
55 ms63 ms
82 ms
73 ms106 ms
3.4×
Search, all columns
737 ms
714 ms737 ms
65 ms
50 ms69 ms
413 ms
366 ms438 ms
11.3×
Filter + sort
205 ms
193 ms206 ms
40 ms
38 ms44 ms
41 ms
40 ms50 ms
5.1×

The chart shows medians. In the table, the big number is the median of 3 runs, each run itself a median of 5 timings, and the small numbers are the fastest and slowest run. “First query” is the first of the 5 timings in each run, before any cache exists.

100,000 rows

At this size both paths are quick, and the difference mostly matters for text. Encoding all 9 columns takes 200 ms. Under 20,000 rows the grid does not start the worker at all: moving the data to another thread would cost more than the sort.

100,000 rows. Row-based and columnar: median of 3 runs, lowest and highest run below. Columnar first query: the first timing of each run, before per-column caches exist. Ratio: row-based median divided by columnar median.
OperationRow-basedColumnarColumnar, first queryRatio
Sort, number column
10 ms
9.4 ms11 ms
5.7 ms
5.5 ms5.8 ms
7.9 ms
7.0 ms9.5 ms
1.8×
Sort, text column
179 ms
174 ms184 ms
5.2 ms
5.0 ms5.5 ms
147 ms
134 ms150 ms
34.5×
Sort, date column
20 ms
20 ms21 ms
4.9 ms
4.5 ms5.0 ms
5.0 ms
4.5 ms5.0 ms
4.1×
Sort, two columns
21 ms
20 ms22 ms
11 ms
10 ms12 ms
11 ms
10 ms12 ms
1.9×
Filter, equality
16 ms
16 ms18 ms
1.8 ms
1.8 ms1.9 ms
3.6 ms
3.5 ms3.8 ms
9.1×
Filter, 3 clauses (AND)
20 ms
20 ms20 ms
8.2 ms
7.4 ms8.9 ms
9.5 ms
7.4 ms11 ms
2.5×
Search, all columns
80 ms
70 ms82 ms
5.9 ms
4.9 ms6.1 ms
17 ms
17 ms18 ms
13.5×
Filter + sort
29 ms
24 ms31 ms
9.5 ms
9.0 ms11 ms
21 ms
20 ms23 ms
3.0×

What these numbers leave out

Talking to the worker
The columnar function is timed in the same process. In a browser the grid also posts the query to the worker and receives the new row order back as a transferred Uint32Array, which is not copied.
Rendering
Not in any number here. Only the rows in view are in the DOM, so a sorted million rows paints the same couple of dozen rows an unsorted one does.
The first encode
A column is encoded the first time a query needs it, and then kept until the data changes. Encoding all 9 columns took 3.3 s at a million rows and 200 ms at 100,000; a sort on one column encodes only that column, while a free-text search needs every visible one. It runs on the main thread because it calls your valueGetter functions, which cannot be sent to a worker. From 100,000 rows up it yields to the browser as it goes, so the page keeps painting.
Other browsers
Node and Chrome share the V8 engine. Firefox and Safari do not, and their numbers will differ.
Noise
This is a laptop, and runs vary. The row-based number sort took anywhere from 208 ms to 309 ms across the three runs, and a set of runs earlier the same day had the columnar number sort at 83 ms instead of 101 ms. Run the script a few times before trusting any single result, including ours.

Run it yourself

The script builds the same seeded dataset every time and imports the engine from npm, so there is nothing of ours on your machine except the package. Pass a row count as the first argument, and --json if you want the raw numbers.

npm install @kanunilabs/datagrid-core
curl -O https://kanunilabs.com/benchmarks/datagrid-pipeline.bench.mjs
node datagrid-pipeline.bench.mjs 1000000
datagrid-pipeline.bench.mjs

Why no other grid is on this page

We publish numbers only for code we can explain. Timing another library means picking its version, its configuration and the shape of its data on its behalf, and each of those can move the result more than the engine does. If you are comparing, run your own data through each candidate. The playground below loads a million rows in your browser, and the script above is short enough to adapt.