Benchmarks

Every number here was measured by the ts-r project and recorded in a commit message, a crate's AGENTS.md, a design record or bench/README.md. Each section says what was measured, on what machine and project, how many runs, the command that reproduces it and the commit that recorded it. Pre-alpha: only the pieces that exist today are measured, and the numbers that miss their targets are shown too. Lower is better unless a chart says otherwise.

ts-r-dev against Vite 8 on excalidraw

excalidraw (about 500 TypeScript modules served from source, 47 dependency entries) served by ts-r-dev and by Vite 8.3.4 with @vitejs/plugin-react 6.1.2. Start is process spawn until the canvas and welcome screen are in the page; cold means an empty ts-r cache and no Vite dependency cache. Both kept React state through all 53 edits with 0 full reloads. SCSS is replaced by an empty stylesheet for both servers (ts-r-dev does not compile it yet).

Start to first render, medianShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-dev, warm538 ms
  • ts-r-dev, cold939 ms
  • Vite 8.3.4, warm1,032 ms
  • Vite 8.3.4, cold1,107 ms
Show as table
Start to first render, median (ms, lower is better)
SetupMedianNotes
ts-r-dev, warm538 ms
ts-r-dev, cold939 msincludes pre-bundling 47 dependency entries into 133 files
Vite 8.3.4, warm1,032 ms
Vite 8.3.4, cold1,107 ms
Save to page updated, p50Shorter is faster, in ms
  • ts-r
  • Other tools
  • target < 30 ms
  • ts-r-dev8.6 ms
  • Vite 8.3.435 ms
Show as table
Save to page updated, p50 (ms, lower is better)
Setupp50p95
ts-r-dev8.6 ms9.0 ms
Vite 8.3.435 ms37.2 ms
Dev server memory after the editsShorter is faster, in MB
  • ts-r
  • Other tools
  • ts-r-dev153 MB
  • Vite 8.3.4364 MB
Show as table
Dev server memory after the edits (MB, lower is better)
SetupRSS after 50 editsAfter first loadNotes
ts-r-dev153 MB151 MBincludes the Node process that runs vite.config
Vite 8.3.4364 MB371 MB
How we measured
Measured
Headless Chrome (a fresh one per page load) drives both servers: 5 cold and 5 warm starts (median), then 50 measured edits of the welcome-screen heading after 3 warm-ups, timed in the browser. RSS is the whole server process group.
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-dev release build; Vite 8.3.4 with @vitejs/plugin-react 6.1.2 pinned in bench/package.json; both servers capped at 4 threads
Input
excalidraw at the commit pinned in bench/repos.toml, with the harness in bench/dev/excalidraw (index.html, main.tsx rendering excalidraw-app/App, vite.config.mjs with its source aliases)
Command
cargo xtask bench --fetch-repos --repos excalidraw && scripts/limit.sh --mem-mb 1024 --time-s 1800 -- node bench/dev/dev.mjs --app excalidraw --server both --mode bench
Recorded in
commit 9c3a371, crates/ts_r_hmr/AGENTS.md, bench/dev/dev.mjs

ts-r-dev: hot reload latency as the app grows

Time from saving a file to the browser having re-imported the changed module and run its callbacks, on generated apps. The plan's target is under 30 ms at p50, staying flat as the project grows. Vite has not been measured on these generated apps yet.

Save to browser updated, p50, as the app growsLower is faster, in ms. Generated apps from 1,000 to 50,000 modules.
Target: under 30 ms
  • 4.9 ms at 1,000 modules
  • 4.8–5.0 ms at 10,000 modules
  • 5.4 ms at 50,000 modules
Show as table
Save to browser updated, p50 (ms, lower is better)
Setupp50p95First save of a fileInitial page load
1,000 modules4.9 ms5.9–6.9 ms7–17 ms0.3 s
10,000 modules4.8–5.0 ms5.3–6.4 ms7–17 ms1.7 s
50,000 modules5.4 ms8.3 ms16 ms13.5 s
How we measured
Measured
Browser-side timing in headless Chrome: 47 measured edits per size after 3 warm-up edits. Ranges are across runs; the bar shows the upper end.
Machine
Apple M-series Mac, macOS arm64, release build (the exact chip was not recorded)
Software
ts-r-dev release build, headless Chrome
Input
Generated JavaScript apps of 1,000, 10,000 and 50,000 modules; one self-accepting module under a 100-wide import tree is edited. The first save of a file goes through FSEvents, later saves through the kqueue fast path.
Command
TS_R_LATENCY_SIZES=1000,10000,50000 cargo test --release -p ts_r_dev --test latency -- --ignored --nocapture
Recorded in
commit 9ed299f, crates/ts_r_hmr/AGENTS.md

ts-r-build against esbuild 0.28.2 on a dedicated Linux machine

three.js x10 (ten copies of three.js, minified IIFE with source maps) on four dedicated Linux cores, from the review box's bench of pull request #42 on 2026-10-24: these are the numbers in the README's benchmark table. Both bundlers produce the same exports from the same input. Without its persistent cache ts-r-build took 962.9 ms against esbuild's 1,409.8 ms; with the cache, 484.6 ms. The shared-laptop runs below are older (commit 2fb2555), and there the two were level without the cache. Peak memory and excalidraw were not measured on this run.

Bundle three.js x10, minifiedShorter is faster, in ms
  • ts-r
  • Other tools
  • esbuild 0.28.21,409.8 ms
  • ts-r-build, persistent cache484.6 ms
  • ts-r-build, no cache962.9 ms
Show as table
Bundle three.js x10, minified (ms, lower is better)
SetupMedian of 10 runs
esbuild 0.28.21,409.8 ms
ts-r-build, persistent cache484.6 ms
ts-r-build, no cache962.9 ms
How we measured
Measured
Wall time of whole processes with hyperfine (cargo xtask bench --repos three-x10), median of 10 runs after 2 warm-ups; peak memory not measured on this run
Machine
Linux x86-64: four cores of the review box's bench machine, a cpuset nothing else is scheduled on while it measures (one bench slot); release build. The CPU model is not recorded with the result
Software
ts-r-build release build of PR #42's head d8a6a97 (merged as 8b6c630), benchmarked against main at 0697824: the two differ only in ts_r_run, which no scenario here runs, with --threads=4; esbuild 0.28.2 pinned in bench/package.json, with its default threads
Input
three.js x10 at the commit pinned in bench/repos.toml (bench/entry.js, minified IIFE with source maps)
Command
The review box's pull request bench (bench.yml): cargo xtask bench --repos trpc,date-fns,rxjs,three-x10
Recorded in
commit 1a20ed6, bench/README.md, bench/scenarios.toml, README.md

ts-r-build against esbuild 0.28.2 on the shared laptop

Bundling two public projects on the shared laptop, with older code than the Linux run above: three.js x10 (ten copies of three.js, minified IIFE with source maps) and the excalidraw app (minified ESM). Only three.js x10 is drawn as bars: both bundlers produce the same exports from the same input. At commit 2fb2555, without its persistent cache, ts-r-build was about as fast as esbuild there: three invocations of 10 runs each gave medians of 231.8, 233.9 and 255.0 ms against esbuild's 262.7, 238.5 and 246.2 ms, so esbuild was faster in the last one, which the bars show. On excalidraw ts-r-build does less work than esbuild: it does not bundle template-literal import() yet, so it leaves out the 61 locale chunks esbuild emits (7.2 MB of output against 9.3 MB). Those runs, the single bundle and the 20 rebuilds, are in the table with that note, not drawn next to esbuild. Rolldown and Vite's production build have not been measured.

Bundle three.js x10, minifiedShorter is faster, in ms
  • ts-r
  • Other tools
  • esbuild 0.28.2246.2 ms
  • ts-r-build, persistent cache132.2 ms
  • ts-r-build, no cache255 ms
Show as table
Bundle three.js x10, minified (ms, lower is better)
SetupMedian of 10 runs
esbuild 0.28.2246.2 ms
ts-r-build, persistent cache132.2 ms
ts-r-build, no cache255 ms
excalidraw (not like-for-like), memory, output and the oracle
Whatts-r-buildesbuild 0.28.2Notes
Bundle excalidraw, minified: no cache / persistent cache255 / 132 ms201 msMedian of 10 runs. Not like-for-like: ts-r-build leaves out the 61 locale chunks esbuild emits (7.2 MB of output against 9.3 MB)
excalidraw: first build plus 20 edit-and-rebuild cycles, one process1,358 ms3,457 ms (context.rebuild)Median of 5 runs. Not like-for-like: every build leaves out the 61 locale chunks
Peak memory, excalidraw, no cache / cached317 / 152 MB624 MBWithout the 61 locale chunks
Peak memory, three.js x10, no cache / cached422 / 244 MB695 MBLast of the three invocations
three.js x10 output7.88 MB (+2.2%), same exports7.71 MB
esbuild's own bundler tests (oracle)832 of 872 compared cases pass (95.4%)referenceCommit 07d2cdd
How we measured
Measured
Wall time of whole processes with hyperfine (cargo xtask bench --repos), median; peak RSS of one extra run (--memory). Rebuilds: examples/build_loop.rs --edit against bench/js/esbuild-rebuild.mjs, editing excalidraw-app/components/AppWelcomeScreen.tsx
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-build release build with 4 threads; esbuild 0.28.2 pinned in bench/package.json. The three.js x10 rows were measured again on 2026-10-21 at commit 2fb2555 (load average 11-14); excalidraw rows are from commit 9c3a371
Input
excalidraw (bench/dev/excalidraw/main.tsx, .woff2 as files, .scss empty) and three.js x10 at the commits pinned in bench/repos.toml
Command
cargo xtask bench --fetch-repos --repos excalidraw,three-x10 && cargo xtask bench --repos excalidraw,three-x10 --memory
Recorded in
commit 9c3a371, bench/README.md, bench/scenarios.toml

ts-r-lint against ESLint with the same rules

tRPC's own ESLint config with every rule ts-r-lint does not implement removed, given to both tools as the same file, on the same 147 files: 48 rules, without type information on either side. Both report 0 problems from those rules. The bars are the run where ts-r-lint evaluates the config in Node every time, as ESLint does; most of its time is that Node process. By default ts-r-lint caches the evaluated config and then takes 15.5 ms, but ESLint has no such step to skip, so that run is in the table, not drawn next to ESLint. With type-aware rules the runs are not comparable yet: on tRPC ts-r-lint's parity gate turns those rules off (its type check reports one error tsc 7 does not), and with the gate off it reports 11 problems ESLint does not.

tRPC, same 48 rules, config evaluated each runShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-lint, config not cached733 ms
  • ESLint 10.10.01,015 ms
Show as table
tRPC, same 48 rules, config evaluated each run (ms, lower is better)
SetupMedian of 5 runsNotes
ts-r-lint, config not cached733 msNode evaluates eslint.config.js on every run
ESLint 10.10.01,015 ms
Cached config, memory, and the type-aware rules
RunTimePeak memoryNotes
ts-r-lint, same 48 rules, config cached (its default)15.5 ms9 MBSkips evaluating the config, which ESLint does on every run, so not drawn next to ESLint
ts-r-lint, same 48 rules, config not cached733 ms336 MBPeak is the Node process that evaluates the config
ESLint, same 48 rules1,015 ms515 MB0 problems from the rules, like ts-r-lint
ts-r-lint, same 80 rules with the 32 type-aware ones, parity gate off, empty cache952 ms335 MBNot like-for-like: 11 problems ESLint does not report (unbound-method 4, no-unnecessary-type-assertion 4, switch-exhaustiveness-check 2, dot-notation 1). By default the parity gate turns these rules off on tRPC
ESLint, same 80 rules (typescript-eslint projectService)2,605 ms973 MB0 problems from the rules
How we measured
Measured
Wall time of the whole command with hyperfine (cargo xtask bench --repos trpc lint-trpc-same-rules), median of 5 runs after 1 warm-up; peak RSS of the largest process in one extra run. Before each tool's runs, bench/js/eslint-same-rules.mjs writes the restricted configs from the binary's --list-rules. Untyped runs: ts-r-lint --no-type-check, ESLint with parserOptions project and projectService off. Problems compared once from each tool's JSON output by file, rule, line and column
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-lint release build (crates as of a7210058) with 4 threads; ESLint 10.10.0 and typescript-eslint 8.70.0 from tRPC's lockfile; Node 24.21.0
Input
tRPC packages/server/src and packages/client/src (147 files) at the commit pinned in bench/repos.toml; tRPC's eslint.config.js limited to the 80 of its 126 configured rules ts-r-lint implements (48 without the type-aware ones)
Command
cargo xtask bench --fetch-repos --repos trpc && RAYON_NUM_THREADS=4 cargo xtask bench --repos trpc lint-trpc-same-rules --memory
Recorded in
commit a6bb4b48, bench/js/eslint-same-rules.mjs, bench/scenarios.toml, bench/README.md

ts-r-lint against ESLint and oxlint on a dedicated Linux machine

The lint scenarios on four dedicated Linux cores, from the review box's bench of pull request #42 on 2026-10-24. The bars are tRPC with the same 48 untyped rules given to both tools, with ts-r-lint evaluating the config in Node on every run, as ESLint does. The other rows are in the table only: the cached config skips a step ESLint has, the type-aware run turns ts-r-lint's parity gate off, and with their own configs ESLint and oxlint run rules ts-r-lint does not (see the shared-laptop sections above for what each run means). Problems were not compared and peak memory was not measured on this run.

tRPC, same 48 rules, config evaluated each runShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-lint, config not cached2,850.7 ms
  • ESLint4,005.4 ms
Show as table
tRPC, same 48 rules, config evaluated each run (ms, lower is better)
SetupMedian of 5 runsNotes
ts-r-lint, config not cached2,850.7 msNode evaluates eslint.config.js on every run
ESLint4,005.4 ms
Cached config, type-aware rules and each project's own config (Linux)
Runts-r-lintOther toolNotes
tRPC, same 48 rules, config cached (ts-r-lint's default)30.6 msESLint 4,005.4 msSkips evaluating the config, which ESLint does on every run, so not drawn next to ESLint. Median of 5 runs
tRPC, same 80 rules with the 32 type-aware ones, empty cache3,588.1 msESLint 10,790.1 msts-r-lint with its parity gate off; not like-for-like (see the laptop section). Median of 5 runs
tRPC server and client sources, own config: type-aware / no type check838.1 / 43.8 msESLint 11,278.7 msESLint also runs plugin rules ts-r-lint lacks. Median of 3 runs
date-fns pkgs/core/src, own config186.6 msoxlint 1,103.3 msDifferent rule set. Median of 5 runs
How we measured
Measured
Wall time of the whole command with hyperfine (cargo xtask bench --repos), median of 5 runs after 1 warm-up (lint-trpc: 3 runs); problems not compared and peak memory not measured on this run
Machine
Linux x86-64: four cores of the review box's bench machine, a cpuset nothing else is scheduled on while it measures (one bench slot); release build. The CPU model is not recorded with the result
Software
ts-r-lint release build of PR #42's head d8a6a97 (merged as 8b6c630), benchmarked against main at 0697824: the two differ only in ts_r_run, which no scenario here runs, with 4 threads; ESLint, typescript-eslint and oxlint from each repo's lockfile
Input
tRPC packages/server/src and packages/client/src (147 files) and date-fns pkgs/core/src at the commits pinned in bench/repos.toml; the restricted tRPC configs are written by bench/js/eslint-same-rules.mjs
Command
The review box's pull request bench (bench.yml): cargo xtask bench --repos trpc,date-fns,rxjs,three-x10
Recorded in
commit 1a20ed6, bench/README.md, bench/scenarios.toml, bench/js/eslint-same-rules.mjs

ts-r-lint against ESLint and oxlint

Each project linted with its own config. Not like-for-like: ESLint also runs plugin rules ts-r-lint does not have (on TypeORM most of its problems come from jsdoc rules), and where ts-r-check's diagnostics differ from tsc's, ts-r-lint runs without type information (its parity gate), so its type-aware column is a type check plus the untyped rules. Because ESLint does more work, these runs are only in the table, with no bars and no totals. The rules themselves match ESLint's and typescript-eslint's test suites (see Correctness).

Each project with its own config, and memory (not like-for-like)
RunTimePeak memoryNotes
tRPC server and client sources: ts-r-lint / no type check186 / 20 ms148 / 9 MBMedian of 3 runs
tRPC: ESLint, type-aware2,566 ms992 MBAlso runs plugin rules ts-r-lint lacks, so not a like-for-like comparison
TypeORM packages/typeorm/src: ts-r-lint / no type check810 / 194 ms380 / 26 MBMedian of 3 runs
TypeORM: ESLint, type-aware6,718 ms1,748 MBMost of its problems come from jsdoc rules ts-r-lint lacks, so not a like-for-like comparison
Playwright core sources: ts-r-lint / no type check725 / 59 ms465 / 17 MB
Playwright core sources: ESLint5,441 ms2,341 MBAlso runs plugin rules ts-r-lint lacks, so not a like-for-like comparison
date-fns pkgs/core/src: ts-r-lint76 ms14 MB
date-fns pkgs/core/src: oxlint (its own config)348 ms301 MBDifferent rule set, so not a like-for-like comparison
How we measured
Measured
Wall time of the whole command with hyperfine (cargo xtask bench --repos), median of 3 to 5 runs; peak RSS of one extra run
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-lint release build with 4 threads; each repo's own ESLint or oxlint from its lockfile
Input
tRPC, TypeORM, Playwright and date-fns at the commits pinned in bench/repos.toml, with their own lint configs
Command
cargo xtask bench --fetch-repos --repos trpc,typeorm,playwright,date-fns && cargo xtask bench --repos trpc,typeorm,playwright,date-fns --memory lint-
Recorded in
commit 9c3a371, bench/README.md, bench/scenarios.toml

ts-r-check against tsc 7.0.2 on a dedicated Linux machine

The check scenarios the review box runs on every pull request, on four dedicated Linux cores: a cold check (no saved history) of three public projects against TypeScript 7.0.2's native tsc, from the bench of pull request #42 on 2026-10-24. These are the numbers in the README's benchmark table. ts-r-check reports exactly tsc's diagnostics on tRPC and date-fns, and on RxJS 6 extra and misses 5 of about 21,900 (2026-10-24, 087afc9); it also skips checks it does not implement yet, so these times will move as the checker grows. With its saved history and nothing changed it took 140.9 ms on RxJS, but 401.0 ms on tRPC and 1,020.4 ms on date-fns, about as long as a cold check there. TypeORM and Playwright are not in the review box's set, and peak memory was not measured on this run: both are in the shared-laptop run below, which is older.

Check three projects, total wall timeShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-check2,674.9 ms
  • tsc 7.0.29,473.0 ms
Show as table
Check three projects, total wall time (ms, lower is better)
SetupSum of the medians
ts-r-check2,674.9 ms
tsc 7.0.29,473.0 ms
Cold check, wall timeShorter is faster, in ms
  • tRPC packages/server474.4 ms
  • date-fns pkgs/core998.4 ms
  • RxJS packages/rxjs1,202.1 ms
Show as table
Cold check, wall time (ms, lower is better)
Setupts-r-check, median of 5tsc 7.0.2
tRPC packages/server474.4 ms979.0 ms
date-fns pkgs/core998.4 ms1,980.1 ms
RxJS packages/rxjs1,202.1 ms6,513.9 ms
Warm runs, and each repo's own TypeScript (Linux)
Projectts-r-check, saved history, nothing changedThe repo's own tsc
tRPC401.0 ms5,176.7 ms
date-fns1,020.4 ms1,970.2 ms
RxJS140.9 ms14,249.3 ms
How we measured
Measured
Wall time with hyperfine (cargo xtask bench --repos), median of 5 runs after 1 warm-up; peak memory not measured on this run. Every tool checks the same wrapper tsconfig (noEmit, not incremental)
Machine
Linux x86-64: four cores of the review box's bench machine, a cpuset nothing else is scheduled on while it measures (one bench slot); release build. The CPU model is not recorded with the result
Software
ts-r-check release build of PR #42's head d8a6a97 (merged as 8b6c630), benchmarked against main at 0697824: the two differ only in ts_r_run, which no scenario here runs; TypeScript 7.0.2 (native tsc) pinned in bench/package.json; each repo's own TypeScript
Input
tRPC packages/server, date-fns pkgs/core and RxJS packages/rxjs at the commits pinned in bench/repos.toml
Command
The review box's pull request bench (bench.yml): cargo xtask bench --repos trpc,date-fns,rxjs,three-x10
Recorded in
commit 1a20ed6, bench/README.md, bench/scenarios.toml, README.md

ts-r-check against tsc 7.0.2 on five projects, shared laptop

Type checking five public projects from the command line, cold (no saved history), against TypeScript 7.0.2's native tsc, on the shared laptop on 2026-10-16 at commit 9c3a371: older code than the Linux run above, with peak memory and two more projects. Some of these repos do not type-check cleanly under TypeScript 7. ts-r-check reports exactly tsc's diagnostics on tRPC, date-fns and TypeORM; on Playwright it reports 2 extra, and on RxJS 6 extra and misses 5 of about 21,900 (2026-10-24, 087afc9). It also skips checks it does not implement yet, so these times will move as the checker grows.

Check all five projects, total wall timeShorter is faster, in ms
  • ts-r
  • Other tools
  • ts-r-check1,936 ms
  • tsc 7.0.23,044 ms
Show as table
Check all five projects, total wall time (ms, lower is better)
SetupSum of the medians
ts-r-check1,936 ms
tsc 7.0.23,044 ms
Cold check, wall timeShorter is faster, in ms
  • tRPC packages/server108 ms
  • date-fns pkgs/core226 ms
  • RxJS packages/rxjs462 ms
  • TypeORM packages/typeorm548 ms
  • Playwright592 ms
Show as table
Cold check, wall time (ms, lower is better)
Setupts-r-check, median of 5tsc 7.0.2
tRPC packages/server108 ms189 ms
date-fns pkgs/core226 ms323 ms
RxJS packages/rxjs462 ms1,280 ms
TypeORM packages/typeorm548 ms567 ms
Playwright592 ms685 ms
Cold check, peak memoryShorter is faster, in MB
  • tRPC packages/server117 MB
  • date-fns pkgs/core216 MB
  • RxJS packages/rxjs458 MB
  • TypeORM packages/typeorm377 MB
  • Playwright463 MB
Show as table
Cold check, peak memory (MB, lower is better)
Setupts-r-check, one runtsc 7.0.2
tRPC packages/server117 MB294 MB
date-fns pkgs/core216 MB558 MB
RxJS packages/rxjs458 MB1,168 MB
TypeORM packages/typeorm377 MB826 MB
Playwright463 MB1,094 MB
Warm runs, and each repo's own TypeScript
Projectts-r-check, saved history, nothing changedThe repo's own tsc
tRPC23 ms1,130 ms
date-fns78 ms324 ms
RxJS57 ms2,468 ms
TypeORM126 ms3,915 ms
Playwright40 ms4,754 ms
How we measured
Measured
Wall time with hyperfine (cargo xtask bench --repos), median of 5 runs; peak RSS of one extra run. Every tool checks the same wrapper tsconfig (noEmit, not incremental)
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-check release build; TypeScript 7.0.2 (native tsc) pinned in bench/package.json; each repo's own TypeScript
Input
tRPC, date-fns, RxJS, TypeORM and Playwright at the commits pinned in bench/repos.toml
Command
cargo xtask bench --fetch-repos --repos trpc,date-fns,rxjs,typeorm,playwright && cargo xtask bench --repos trpc,date-fns,rxjs,typeorm,playwright --memory check-
Recorded in
commit 9c3a371, bench/README.md, bench/scenarios.toml

ts-r-lsp with our checker against tsserver and TypeScript 7's LSP

Under 500 MB on these projects; large ones not measured

A scripted LSP client on three public projects: date-fns (1,641 sources), RxJS (231) and tRPC (154). ts-r-lsp uses far less memory than tsserver and TypeScript 7's native LSP, with every source open too, but it is slower than TypeScript 7's LSP to first diagnostics on all three and per keystroke on two of them (it re-checks each edit on a fresh checker). It also reports a few diagnostics tsc does not on the opened RxJS and tRPC files. These are medium projects: the 500 MB target is for large ones, which have not been measured.

RxJS: memory with all 231 sources openShorter is faster, in MB
  • ts-r
  • Other tools
  • target ≤ 500 MB
  • ts-r-lsp377 MB
  • TypeScript 7 LSP (tsc --lsp)820 MB
  • tsserver (the repo's TypeScript)1,091 MB
Show as table
RxJS: memory with all 231 sources open (MB, lower is better)
SetupRSS
ts-r-lsp377 MB
TypeScript 7 LSP (tsc --lsp)820 MB
tsserver (the repo's TypeScript)1,091 MB
date-fns: memory with all 1,641 sources openShorter is faster, in MB
  • ts-r
  • Other tools
  • ts-r-lsp232 MB
  • TypeScript 7 LSP (tsc --lsp)710 MB
Show as table
date-fns: memory with all 1,641 sources open (MB, lower is better)
SetupRSSNotes
ts-r-lsp232 MB
TypeScript 7 LSP (tsc --lsp)710 MBtsserver did not publish diagnostics within the harness's 120 s
tRPC router.ts: keystroke to diagnostics, p50Shorter is faster, in ms · TypeScript 7 is faster here
  • ts-r
  • Other tools
  • TypeScript 7 LSP6.2 ms
  • ts-r-lsp9.4 ms
  • tsserver24.3 ms
Show as table
tRPC router.ts: keystroke to diagnostics, p50 (ms, lower is better)
Setupp50p95
TypeScript 7 LSP6.2 ms7.4 ms
ts-r-lsp9.4 ms10.5 ms
tsserver24.3 ms29.3 ms
tRPC router.ts: first diagnosticsShorter is faster, in ms · TypeScript 7 is faster here
  • ts-r
  • Other tools
  • TypeScript 7 LSP58 ms
  • ts-r-lsp224 ms
  • tsserver800 ms
Show as table
tRPC router.ts: first diagnostics (ms, lower is better)
SetupTime
TypeScript 7 LSP58 ms
ts-r-lsp224 ms
tsserver800 ms
Per project
Measurets-r-lsptsserverTypeScript 7 LSP
date-fns format/index.ts: first diagnostics416 mstimed out168 ms
date-fns: keystroke to diagnostics p50 / p9514.1 / 14.7 mstimed out5.0 / 5.8 ms
date-fns: memory, one file / all 1,641 open164 / 232 MBtimed out469 / 710 MB
RxJS merge-map.ts: first diagnostics155 ms953 ms108 ms
RxJS: keystroke to diagnostics p50 / p9512.2 / 12.8 ms28.6 / 35.1 ms13.3 / 17.2 ms
RxJS: memory, one file / all 231 open138 / 377 MB840 / 1,091 MB397 / 820 MB
tRPC router.ts: memory, one file / all 154 open95 / 165 MB571 / 757 MB477 / 480 MB
Diagnostics on the opened file (RxJS / tRPC)2 / 3not recorded0 / 2
How we measured
Measured
A scripted LSP client: first diagnostics, keystroke to diagnostics (30 keys, 150 ms apart) on one file, then memory with that file open and with every source of the project open (5 s settle); one run per tool
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-lsp release build with the ts-r backend and the tsc parity check off; each repo's own tsserver; TypeScript 7.0.2's native LSP (tsc --lsp) pinned in bench/package.json
Input
date-fns, RxJS and tRPC at the commits pinned in bench/repos.toml; the files and source directories are each repo's [repo.editor] set
Command
cargo xtask bench --fetch-repos --repos date-fns,rxjs,trpc && cargo xtask bench --editor --repos date-fns,rxjs,trpc (prints the commands; the editor-latency.js ones were run)
Recorded in
commit 9c3a371, bench/README.md, editors/vscode/bench/editor-latency.js

Lint in the editor: ts-r-lsp against the ESLint extension's server

Time from a keystroke to new lint diagnostics, and server memory, in tRPC's largest server source (22 KB, resolveResponse.ts) with the repo's own ESLint config and all 154 sources open. ts-r runs the rules it implements: 35 of the config's rules are not run in the editor (17 not implemented yet, 17 from plugins, 1 type-aware), so the ESLint server does more work and this is shown as a table rather than as bars.

Lint in the editor (not like-for-like: the ESLint server runs 35 rules ts-r skips)
ServerKeystroke to diagnostics, p50 / p95RSS, all 154 sources open
ts-r-lsp --lint-only15.6 / 15.8 ms (includes the default 10 ms debounce)10.6 MB
ESLint server 3.0.34 (also runs the 35 rules ts-r skips)53.3 / 58.6 ms2,296 MB
How we measured
Measured
A scripted LSP client opens every source, then types 300 keystrokes 100 ms apart into a comment and times didChange to diagnostics; RSS of the server at the end; one run each
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r-lsp release build; the ESLint VS Code extension's server 3.0.34 run headlessly with pull diagnostics; the repo's own ESLint
Input
tRPC at the commit pinned in bench/repos.toml: packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts, with packages/server/src and packages/client/src open
Command
scripts/limit.sh -- node editors/vscode/bench/lint-keystrokes.js ts-r target/release/ts-r-lsp target/bench-repos/trpc <file> --open-all --src-dirs packages/server/src,packages/client/src (eslint mode with eslintServer.js for the other)
Recorded in
commit 9c3a371, bench/README.md, editors/vscode/bench/lint-keystrokes.js

ts-r-lsp: two editor windows on your own TypeScript

ts-r-lsp can also run your project's own TypeScript behind one shared server per workspace, instead of our checker. This measures total memory for two editor windows on tRPC in that mode (two files open in each, one window in packages/server and one in packages/client). The shared server holds both windows' projects, so it is larger than one plain tsserver but smaller than two.

Memory for two editor windowsShorter is faster, in MB
  • ts-r
  • Other tools
  • ts-r-lsp, one shared server698 MB
  • Two plain tsservers838 MB
  • VS Code default setup1,451 MB
Show as table
Memory for two editor windows (MB, lower is better)
SetupTotal memoryNotes
ts-r-lsp, one shared server698 MBtwo editor fronts plus one daemon with one tsserver
Two plain tsservers838 MBone tsserver per window
VS Code default setup1,451 MBper window: semantic tsserver with automatic type acquisition, plus a syntax server
How we measured
Measured
Total resident memory (RSS) of every process in each setup, summed with their children, after both windows have diagnostics for every open file; one run
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
TypeScript 6.0.3 (tRPC's tsserver) as the type backend
Input
tRPC at the commit pinned in bench/repos.toml; window A opens packages/server/src/unstable-core-do-not-import/router.ts and packages/server/src/index.ts, window B packages/client/src/index.ts and packages/client/src/links/httpBatchLink.ts
Command
cargo build --release -p ts_r_lsp --example two_windows && scripts/limit.sh -- target/release/examples/two_windows target/bench-repos/trpc
Recorded in
commit 9c3a371, bench/README.md, crates/ts_r_lsp/examples/two_windows.rs

ts-r-run: running a TypeScript file

Time from starting the command to the program exiting, against tsx and Node's own type stripping. node -e 0 is the floor: starting Node with no TypeScript at all.

Run hello.tsShorter is faster, in ms
  • ts-r
  • Other tools
  • Baseline
  • node -e 0 (floor)23 ms
  • ts-r-run, warm cache29 ms
  • ts-r-run, empty cache32 ms
  • node --experimental-strip-types50 ms
  • tsx 4.2379 ms
Show as table
Run hello.ts (ms, lower is better)
SetupMedian time
node -e 0 (floor)23 ms
ts-r-run, warm cache29 ms
ts-r-run, empty cache32 ms
node --experimental-strip-types50 ms
tsx 4.2379 ms
Run a 1,000-module programShorter is faster, in ms
  • ts-r
  • Other tools
  • Baseline
  • node -e 0 (floor)24 ms
  • ts-r-run, warm cache101 ms
  • ts-r-run, empty cache137 ms
  • node --experimental-strip-types215 ms
  • tsx 4.23270 ms
Show as table
Run a 1,000-module program (ms, lower is better)
SetupMedian time
node -e 0 (floor)24 ms
ts-r-run, warm cache101 ms
ts-r-run, empty cache137 ms
node --experimental-strip-types215 ms
tsx 4.23270 ms
How we measured
Measured
Wall time, median of 15 runs
Machine
Apple M-series Mac, macOS arm64, release build (the exact chip was not recorded)
Software
Node 24.21.0; tsx 4.23 run as its binary (npx tsx adds about 400 ms); ts-r-run with 4 compile threads and source maps on
Input
A generated project of 1,000 TypeScript modules (interfaces, classes, typed functions, .ts imports so Node type stripping can run it too), and hello.ts
Command
cargo build --release -p ts_r_run --bin ts-r-run --example bench && TS_R_RUN_THREADS=4 scripts/limit.sh -- target/release/examples/bench --runs 15
Recorded in
commit e92e486, crates/ts_r_run/AGENTS.md, docs/design/0002-transform-and-codegen.md

Ecosystem: the top 1,000 npm packages

cargo xtask ecosystem runs each stage that applies to a package and compares it with the reference tool. Pass rate is pass / (pass + fail + error); skipped packages have nothing to compare. The check stage imports each package with skipLibCheck off and passes it when ts-r-check prints the same diagnostics (file, line, column, code) as tsc 7; in the 57 packages that fail, 121 of the 1,460 compared diagnostics differ (119 that only ts-r-check reports, 2 that only tsc reports). The parse stage fails 30 packages on grammar errors that ts-r's parser reports in JavaScript files Node and tsc accept, most often TS1313 (an empty statement as the body of an if). Two of the four selected packages' own test suites pass with ts-r swapped in; the other two (chalk and commander) fail only on an extra TS2661 from ts-r-check, and pass with its errors ignored.

Packages passing each stageLonger is better
  • Bundle and run (vs esbuild)100%
  • Transform TypeScript (vs tsc)100%
  • Parse every file97%
  • Check an import of it (vs tsc 7)92%
  • Own test suite50%
Show as table
Packages passing each stage (%, higher is better)
SetupPass ratePass / compared
Bundle and run (vs esbuild)100%915 / 915
Transform TypeScript (vs tsc)100%48 / 48
Parse every file97%959 / 989
Check an import of it (vs tsc 7)92%652 / 709
Own test suite50%2 / 4
Items inside the packages, and bundle size
StageItems comparedItems failing
parse (files)39,10156
transform (files)1,1560
bundle (entries)1,2170
check (diagnostics, ours and tsc's)1,460121
tests (suites)42
Bundle size, 914 packages, minifiedts-r-build 98,048.1 KiBesbuild 101,020.8 KiB (ts-r −2.9%)
How we measured
Measured
Pass, fail, error or skip per package per stage, compared against the reference tool's output
Machine
Apple M-series MacBook (macOS arm64, 16 cores, 48 GB), shared with other jobs while measuring; release build, 4 threads
Software
ts-r at commit ea41eac; esbuild 0.28.2, Node 24.21.0, tsc 7.0.2
Input
The top 1,000 packages of ecosystem/packages.toml (list of 2026-10-14); one run of 776 s on 2026-10-24
Command
cargo xtask ecosystem --top 1000 --stages parse,transform,bundle,check --tests --jobs 3 (3 packages at a time; see ecosystem/README.md)
Recorded in
commit 8b5f8ea, ecosystem/STATUS.md, ecosystem/README.md

Parser, binder, cache and launcher

Throughput of the shared engine parts, on public code. These have no comparison bars yet: the side-by-side parser benchmark against Oxc and esbuild is set up in bench/scenarios.toml but ts-r has no parse-only command for it yet.

WhatResultMeasured on
Parse, single threadabout 225 MB/s (30,000 files/s)tRPC including node_modules: 60,419 files, 456.5 MB; 4 files report syntax errors (not yet compared with tsc). Apple M-series shared laptop, 2026-10-16; command in crates/ts_r_parser/AGENTS.md.
Parse and bind, 4 threadsabout 44,600 files/stRPC: 11,937 project and node_modules declaration files, 0 panics. Apple M-series shared laptop, 2026-10-16; command in crates/ts_r_binder/AGENTS.md.
Shared cache, warm lookup of 10,000 entries (4 KiB each)27 ms on one thread, 8.7 ms on 4 threadscargo run --release -p ts_r_cache --example bench. Apple M-series. Commit 8d32516.
ts-r launcher overheadabout 0.5 ms (ts-r --version: 2.1 ms)500 runs via Python subprocess, whose own spawn of /usr/bin/true is 1.6 ms median. Apple M-series. Commit 8d32516.
ts-r --version on four dedicated Linux cores2.03 ms (esbuild --version 3.93 ms, tsc 7 --version 61.3 ms)Median of 30 runs (cargo xtask bench version-startup), the review box's bench of pull request #42 (d8a6a97, merged as 8b6c630), 2026-10-24; recorded in bench/README.md.

Correctness against tsc 7.0.2, esbuild and ESLint

Speed only counts if the output is right. These compare ts-r with the tool it replaces as the reference. The rows come from data/conformance.json: the same numbers as the Compatibility and status page, each quoting the file that records it.

WhatResultMeasured on
Parse, print, parse again17,740 files ok, 0 mismatches (1,472 skipped: syntax errors)TypeScript's tests/cases. Commit ea41eac.
tsconfig loading114 of 114 --showConfig outputs match; 111 of 114 with the same diagnostics (3 differ in a message's text)tsc 7.0.2 --showConfig and config diagnostics on the five public bench repos (114 tsconfig files). Commit ea41eac.
Module resolution158,174 resolutions, 10 mismatches (one require of vitest, in two tRPC configs)tsc 7.0.2 --traceResolution on the five public bench repos (114 configs). Commit ea41eac.
JavaScript output (emit)1,673 match, 61 differtsc 7.0.2's emit baselines (below ES2022, outFile, AMD/System/UMD and syntax errors skipped). Commit 493fc13.
Binder diagnostics418 of 464 tests exact; 1,720 of 1,839 diagnostics foundtsc 7.0.2 baselines for the binder's codes (12,783 variants). Commit 4224610.
Type checker97.0% exact (all diagnostics and chains); 191 variants with a diagnostic tsc does not report, 10 of them in tests where tsc reports nothingtsc 7.0.2 conformance baselines, 12,750 compared test variants. Commit 9b5f8f2.
Lint rules15,495 of 15,517 cases match (8,597 of 8,597 without types; 6,898 of 6,920 type-aware)every case of the published ESLint and typescript-eslint rule tests (154 rules, 51 type-aware). Commit 087afc9.
Bundler832 of 872 compared cases pass (95.4%); 198 skippedesbuild 0.28.2's end-to-end bundler tests. Commit 07d2cdd.

Not measured yet

The plan compares every tool against the one it replaces. These comparisons have no numbers yet:

The raw data for this page is bench.json. Each tool’s page (Tools) draws the sections that belong to it.