how it works
Lattice Grid holds data in columns, not rows.
Last updated 17 August 2026
A grid of a hundred thousand rows and thirty columns is three million values. How those values are laid out in memory decides what the grid can do with them, and it is the one decision everything else here follows from.
One array per column
Lattice Grid stores each column as a single array. Numbers go into a
Float64Array, eight flat bytes per value with no boxing.
Repeated strings become small integer codes into a per-column dictionary,
so a status column with six distinct values costs four bytes a row rather
than a pointer to a string. Booleans are bits in a bitset. Three million
values, one contiguous allocation per column.
What that buys
Sorting
The sort key is a single contiguous array, and the result is a permutation of row indices which the other twenty-nine columns are then read through. There is no property access per comparison and no unboxing. For dictionary-coded columns the keys are small integers, which admits a radix sort: linear passes over the key array rather than comparisons.
Filtering
A filter produces a bitmask, one bit per row, rather than a new array of row references. That is 12.5 kilobytes for a hundred thousand rows, allocated once and reused. Combining filters is a bitwise AND over machine words rather than a second pass building a second array, so chaining filters allocates nothing after the first mask.
Measured on 100,000 rows by 30 columns: one condition in 3 ms, five chained conditions in 5 ms. Four extra conditions cost about two milliseconds between them, which is the bitmask arithmetic showing up in the numbers rather than in a diagram.
Where the win lands
Scrolling is table stakes: Lattice Grid virtualises the viewport and recycles DOM like any serious grid. The separation shows up the moment an operation touches the whole dataset, sort, filter, group and aggregate, where the work is done over contiguous arrays and intermediate results are masks and index permutations rather than fresh arrays of objects.
The trade-off we made
Columnar storage front-loads the work. Lattice Grid transcodes your data once on load. On 100,000 rows by 30 columns, 3,000,000 values, that is 370 ms on Chrome 151 on an Apple M2 Pro, and every sort, filter and aggregate after that is paid for by that one pass. It is what lets the grid own a typed copy rather than reach back into your objects on every operation.
So if your application mutates row objects in place and expects the grid
to notice by reference, that pattern does not translate. Go through
grid.rows.apply() instead.
Columns holding nested objects or mixed types use plain object storage, so they behave exactly as they would in a row-based grid. The columnar gains apply to the typed columns alongside them.
Measuring it yourself
Every figure here is a median of five runs, reading the result back each time so the pipeline has run rather than been scheduled. Machine and shape are stated because both change the answer, and a number without them is not a claim anyone can check.
The grid on the home page reports its own render time in your browser rather than ours, and the playground lets you change an option and watch the figure move. Development on localhost is free and needs no key, so the quickest way to test any of this is to point it at your own data and see.
Open the playground Free on localhost. See pricing for shipping.