Dedicated memory and row-model operation benchmarks for similar TanStack Table v8 and v9 React examples.
Each benchmark is an independent Vite example with its own package.json, mirroring the structure of the existing TanStack examples:
examples/v8/virtualized-rowsexamples/v8/virtualized-columnsexamples/v8/paginated-rowsexamples/v8/kitchen-sinkexamples/v8/core-row-modelexamples/v8/sorting-row-modelexamples/v8/filtering-row-modelexamples/v8/grouping-row-modelexamples/v8/expanding-row-modelexamples/v8/pagination-row-modelexamples/v8/faceting-row-modelexamples/v8/selection-row-modelexamples/v9/virtualized-rowsexamples/v9/virtualized-columnsexamples/v9/paginated-rowsexamples/v9/kitchen-sinkexamples/v9/core-row-modelexamples/v9/sorting-row-modelexamples/v9/filtering-row-modelexamples/v9/grouping-row-modelexamples/v9/expanding-row-modelexamples/v9/pagination-row-modelexamples/v9/faceting-row-modelexamples/v9/selection-row-model
Shared deterministic data, config parsing, and CSS live in shared/src.
pnpm installKeep the tracked examples/v9/* examples on the latest published v9 beta before using them as the baseline for PR comparisons. Update every v9 example that depends on @tanstack/react-table and/or @tanstack/table-core, then refresh only the tracked v9 workspace importers in the lockfile:
VERSION=9.0.0-beta.21
for d in examples/v9/*; do
if [ -f "$d/package.json" ]; then
node -e "const p=require('./$d/package.json'); process.exit(p.dependencies?.['@tanstack/react-table'] ? 0 : 1)" &&
(cd "$d" && npm pkg set "dependencies.@tanstack/react-table=$VERSION")
node -e "const p=require('./$d/package.json'); process.exit(p.dependencies?.['@tanstack/table-core'] ? 0 : 1)" &&
(cd "$d" && npm pkg set "dependencies.@tanstack/table-core=$VERSION")
fi
done
pnpm install --lockfile-only --filter './examples/v9/**'To compare a table PR against the current v9 baseline, copy the tracked v9 examples to a temporary folder and install the PR preview packages into the copy. Use a unique package name for each copied example so pnpm workspace discovery does not see duplicate package names:
PR=6347
TEMP="examples/v9-pr-temp-$PR"
cp -R examples/v9 "$TEMP"
for d in "$TEMP"/*; do
if [ -f "$d/package.json" ]; then
name="benchmark-table-v9-pr-$PR-$(basename "$d")"
(cd "$d" && npm pkg set "name=$name")
(cd "$d" && npm install --force --prefer-online \
"https://pkg.pr.new/TanStack/table/@tanstack/table-core@$PR" \
"https://pkg.pr.new/TanStack/table/@tanstack/react-table@$PR")
fi
doneThese v9-pr-temp-* folders are intended as local benchmark fixtures. Do not treat their generated package locks, builds, or result files as part of the normal tracked benchmark baseline unless a specific comparison needs to be preserved.
pnpm bench:memoryUseful flags:
pnpm bench:memory -- --iterations 5 --overscan 5Run only one benchmark group:
pnpm bench:memory -- --benchmark rows
pnpm bench:memory -- --benchmark columns
pnpm bench:memory -- --benchmark paginated-rows
pnpm bench:memory -- --benchmark kitchen-sinkCapture beginning/end heap snapshots for the first iteration of each v8/v9/config combination:
pnpm bench:memory -- --heapSnapshots trueBy default snapshots are captured only for configs with at most 10,000 estimated cells, because large browser heap snapshots can be multiple GB and impractical to create or parse locally. Adjust with:
pnpm bench:memory -- --heapSnapshots true --maxSnapshotCells 100000Smooth scroll phases are captured only for configs with at most 1,000,000 estimated cells by default. Larger configs still record initial and instant-scroll phases, plus a smooth-scroll-skipped marker. Adjust with:
pnpm bench:memory -- --maxSmoothScrollCells 10000000The runner builds each independent example, starts vite preview for one example at a time, opens it in a fresh Chromium context, and records:
JSHeapUsedSizebefore forced GCJSHeapUsedSizeafter forced GC- memory reclaimed by forced GC
- DOM document, node, and event listener counts
- rendered row and cell counts for equivalence checks
It measures these phases:
Virtualized examples:
initialinstant-middle-scrollinstant-end-scrollsmooth-middle-scrollsmooth-end-scroll
Paginated examples:
initialnext-pagelast-page
Results are written to results/*.json, results/*.csv, and results/*.html.
Run core, filtering, sorting, grouping, expanding, pagination, faceting, and selected-row-model benchmarks:
pnpm bench:row-modelUseful flags:
pnpm bench:row-model -- --iterations 5 --warmups 0
pnpm bench:row-model -- --rowModel sorting
pnpm bench:row-model -- --rowModel filtering --scenario includesString
pnpm bench:row-model -- --rowModel grouping --scenario sum
pnpm bench:row-model -- --rowModel faceting --scenario statusUnique
pnpm bench:row-model -- --rows 1000,20000,100000,400000
pnpm bench:row-model -- --includeV9Only false
pnpm bench:row-model -- --pipeline fullThe row-model runner builds the matching v8 and v9 row-model examples, starts vite preview for one example at a time, opens the app in Chromium, and calls a headless browser benchmark API exposed by the example. Each sample creates a fresh table instance and times the target getter for that scenario.
By default, --pipeline stage primes upstream row models before timing so the measurement isolates the target stage. Use --pipeline full to time the target getter from a fresh table instance without priming predecessors.
The default row-count matrix is 1,000, 20,000, 100,000, and 400,000 rows. The row-model runner ignores row counts above 400,000 to keep memory pressure from dominating the measurements. Smaller row counts are useful for smoke tests and for catching low-row-count overhead, but sub-millisecond timings can produce noisy percentages.
Row-model benchmark data is generated once from seeded Faker data into shared/generated/rowModelFlatData.json and shared/generated/rowModelTreeData.json before the runner builds examples. The flat fixture powers all non-expanding scenarios. The tree fixture powers expanding scenarios with deterministic nested subRows.
Generate the standalone fixtures manually before running a row-model example directly:
pnpm generate:row-model-data -- --rows 400000Long runs print progress for fixture generation, example builds, preview server startup, each version, and each row-model/row-count group.
Focused row-model scenarios:
- core:
flat - sorting:
alphanumeric,alphanumericCaseSensitive,text,textCaseSensitive,datetime,basic, eachascanddesc - filtering: built-in v8/v9 filterFns plus v9-only filterFns when enabled
- grouping:
sum,min,max,extent,mean,median,unique,uniqueCount,count - expanding:
collapsed,partialExpanded,allExpanded - pagination:
firstPage10,middlePage50,lastPage100 - faceting:
statusUnique,alphanumericUnique,scoreMinMax,statusFacetedRowsWithTextFilter - selection:
selectedCore1Percent,filteredSelected10Percent,groupedSelected10Percent
For grouping scenarios, the benchmark reads grouped aggregate values inside the timed section so the aggregation functions are actually executed. Row selection scenarios measure selected-row-model derivation only; row toggle/update interaction costs are intentionally out of scope for this runner. The grouped selected-row-model scenario currently documents a v8/v9 semantic difference: v8 derives from the grouped row model, while v9 beta.29 derives from the core row model.
Row-model benchmark results are written to results/row-model-*.json, results/row-model-*.csv, and results/row-model-*.html.
The examples intentionally use the same React, React DOM, TanStack Virtual, Vite, and deterministic data generator. The main intended variable is @tanstack/react-table v8 versus v9.
Default sample size is 5 iterations.
Default dimensions:
Rows benchmark:
10rows x8columns1,000rows x8columns100,000rows x8columns1,000,000rows x8columns
Paginated rows benchmark:
10rows x8columns1,000rows x8columns100,000rows x8columns1,000,000rows x8columns
Kitchen sink benchmark:
10rows x8columns1,000rows x8columns100,000rows x8columns1,000,000rows x8columns
Columns benchmark:
10rows x10columns100rows x100columns100rows x1,000columns100rows x10,000columns