Skip to content

Repository files navigation

TanStack Table Benchmark Examples

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-rows
  • examples/v8/virtualized-columns
  • examples/v8/paginated-rows
  • examples/v8/kitchen-sink
  • examples/v8/core-row-model
  • examples/v8/sorting-row-model
  • examples/v8/filtering-row-model
  • examples/v8/grouping-row-model
  • examples/v8/expanding-row-model
  • examples/v8/pagination-row-model
  • examples/v8/faceting-row-model
  • examples/v8/selection-row-model
  • examples/v9/virtualized-rows
  • examples/v9/virtualized-columns
  • examples/v9/paginated-rows
  • examples/v9/kitchen-sink
  • examples/v9/core-row-model
  • examples/v9/sorting-row-model
  • examples/v9/filtering-row-model
  • examples/v9/grouping-row-model
  • examples/v9/expanding-row-model
  • examples/v9/pagination-row-model
  • examples/v9/faceting-row-model
  • examples/v9/selection-row-model

Shared deterministic data, config parsing, and CSS live in shared/src.

Install

pnpm install

v9 Version Maintenance

Keep the tracked examples/v9/* examples on the latest published v9 release 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
fordin examples/v9/*;doif [ -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")
fidone
pnpm install --lockfile-only --filter './examples/v9/**'

PR Preview Setup

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"fordin"$TEMP"/*;doif [ -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")
fidone

These 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.

Run

pnpm bench:memory

Useful flags:

pnpm bench:memory -- --iterations 5 --overscan 5

Run 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-sink

Capture beginning/end heap snapshots for the first iteration of each v8/v9/config combination:

pnpm bench:memory -- --heapSnapshots true

By 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 100000

Smooth 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 10000000

The runner builds each independent example, starts vite preview for one example at a time, opens it in a fresh Chromium context, and records:

  • JSHeapUsedSize before forced GC
  • JSHeapUsedSize after 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:

  • initial
  • instant-middle-scroll
  • instant-end-scroll
  • smooth-middle-scroll
  • smooth-end-scroll

Paginated examples:

  • initial
  • next-page
  • last-page

Results are written to results/*.json, results/*.csv, and results/*.html.

React Compiler Benchmarks

Compare TanStack Table v9 with React Compiler enabled and disabled:

pnpm bench:react-compiler

The runner builds identical source in four production variants: normal and React profiling builds, each with the compiler on and off. It exercises both a broad useTable owner subscription and fine-grained Subscribe boundaries. The default workload uses 10,000 source rows, 100 rendered rows, 4x CPU throttling, two warmups, and 20 fresh samples for each of these updates:

  • an unrelated parent state update
  • selecting one row
  • sorting a column
  • changing pages
  • replacing the data array

Useful flags:

pnpm bench:react-compiler -- --iterations 30 --cpuThrottle 4
pnpm bench:react-compiler -- --strategy owner --scenario selection
pnpm bench:react-compiler -- --rows 50000 --pageSize 250

Normal production builds measure time from the interaction through React's layout-effect phase, time through two animation frames, and Chrome task/script duration. Production profiling builds measure React <Profiler> actual duration and commit count. The runner also reports raw and gzip JavaScript size. Every timed interaction verifies that the expected DOM state changed, so a stale render cannot be recorded as a performance improvement.

Results are written to results/react-compiler-*.json, .csv, and .html. Negative compiler-on percentages mean less time or work. Read results per scenario and subscription strategy; do not combine them into a single score.

Reference Result

The August 16, 2026 reference run used TanStack Table 9.0.0, React 19.2.7, React Compiler 1.0.0, Chromium 149.0.7827.55, an Apple M5 Pro, and the default 4x CPU throttle. These are the median interaction-to-layout-effect commit times from 20 alternating-order samples, in milliseconds:

UpdateOwner: off → onChangeFine-grained: off → onChange
Unrelated parent update3.75 → 0.50-87%3.50 → 0.50-86%
Select one row4.00 → 1.30-68%0.90 → 0.95+6%
Sort8.35 → 7.40-11%8.00 → 7.35-8%
Paginate8.30 → 8.35+1%8.00 → 8.35+4%
Replace all data8.10 → 8.55+6%8.30 → 8.50+2%

The compiler eliminated nearly all measured React render duration for the unrelated parent update and reduced broad-owner selection render duration by 89%. Fine-grained selection was already below one millisecond without the compiler, and full-invalidating updates were effectively unchanged. The compiler-on production JavaScript was 1,026 bytes larger gzip (76,313 → 77,339 bytes, +1.3%). These results describe this fixture and environment, not a universal speedup guarantee.

Row Model Benchmarks

Run core, filtering, sorting, grouping, expanding, pagination, faceting, and selected-row-model benchmarks:

pnpm bench:row-model

Useful 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 full

The 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 400000

Long 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, each asc and desc
  • 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.

Notes

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:

  • 10 rows x 8 columns
  • 1,000 rows x 8 columns
  • 100,000 rows x 8 columns
  • 1,000,000 rows x 8 columns

Paginated rows benchmark:

  • 10 rows x 8 columns
  • 1,000 rows x 8 columns
  • 100,000 rows x 8 columns
  • 1,000,000 rows x 8 columns

Kitchen sink benchmark:

  • 10 rows x 8 columns
  • 1,000 rows x 8 columns
  • 100,000 rows x 8 columns
  • 1,000,000 rows x 8 columns

Columns benchmark:

  • 10 rows x 10 columns
  • 100 rows x 100 columns
  • 100 rows x 1,000 columns
  • 100 rows x 10,000 columns

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages