TypeScript 7.0 replaced the compiler's JavaScript implementation with a native port written in Go, and Microsoft's own numbers describe a compile that dropped from 125.7 seconds to 10.6 seconds, a gain of roughly 12x. I wanted to know whether that holds on a codebase I didn't choose for a marketing slide, so I checked out a real project and ran all three compilers against it myself: TypeScript 6.0.3, the last release before the rewrite, 7.0.2, the first stable native release, and 7.1.0-beta, which shipped eleven days ago.
I used nestjs/nest, cloned fresh and with its real dependencies installed. Its root tsconfig.json pulls in the packages, the integration tests, and the samples: 2,934 files once @types/node and the rest of the dependency graph are counted, about 353,000 lines. tsc --noEmit against that tree is a reasonable stand-in for what a CI job or a pre-commit hook does every day.
The headline number
Three runs each, fastest reported, same machine, same checkout:
| Compiler | Fastest run | Peak memory |
|---|---|---|
| TypeScript 6.0.3 | 12.42s | 1.13 GB |
| TypeScript 7.0.2 | 4.95s | 1.86 GB |
| TypeScript 7.1.0-beta | 2.76s | 1.06 GB |
7.0.2 is 2.5x faster than 6.0.3. 7.1.0-beta is 4.5x faster. Both are real, both are worth having, and neither is the 8-12x Microsoft advertises. The VS Code benchmark that produced that number is presumably a bigger, more type-heavy codebase than a mid-sized Node framework, and benchmarks run on your own code will usually look more modest than the one in the release post. If your project looks more like NestJS than like the TypeScript compiler's own source, plan around 2.5x to 4.5x, not 12x.
The number that argues against the headline
Peak memory went the wrong way for the first stable release. 7.0.2 used more memory than the compiler it replaced, not less, consistently across three runs:
6.0.3: Maximum resident set size: 1,119,044 – 1,128,916 KB
7.0.2: Maximum resident set size: 1,778,072 – 1,863,628 KB
7.1.0-beta: Maximum resident set size: 1,013,640 – 1,056,968 KB
A native rewrite is supposed to use less memory than a JavaScript one doing the same job, and in 7.1.0-beta it does, coming in slightly under the old compiler. But 7.0.2, the release most people will actually install because it's the one tagged latest, used about 65% more memory than 6.0.3 at the same task. A CI runner sized around TypeScript 6's memory footprint could OOM on 7.0.2 and recover on 7.1 once that's out of beta. I don't have an explanation for the regression beyond what I could observe: it happened, consistently, on this codebase, and it reversed itself in the next beta.
What it refuses
The old compiler exposed a JavaScript API: require('typescript') gave you createProgram, transpileModule, createSourceFile, and anything built on top of those. That's gone in 7.0.2. The package's default export is now this:
const { version } = require("../package.json");
exports.version = version;
exports.versionMajorMinor = "7.0";
That's it. No createProgram. I confirmed it wasn't a module resolution problem on my end by installing ts-node 10.9.2 and pointing it at TypeScript 7.0.2:
TypeError: Cannot read properties of undefined (reading 'fileExists')
at readConfig (ts-node/dist/configuration.js:91:33)
ts.sys doesn't exist, because ts is just { version, versionMajorMinor }. Any tool that calls into the compiler programmatically rather than shelling out to tsc breaks the same way: ts-node, ts-jest, and the type-aware rules in typescript-eslint all depend on createProgram.
I expected 7.1.0-beta to fix this, because the beta's headline feature is a stabilized API, and coverage of the release describes it as unblocking Vue, Svelte, and Angular tooling. It does ship a new API, under typescript/sync and typescript/async rather than 7.0's typescript/unstable/sync. I checked the package's export map directly:
7.0.2: "./unstable/sync": "./dist/api/sync/api.js"
7.1.0-beta: "./sync": "./dist/api/sync/api.js"
But the default . export is still just version.cjs in 7.1.0-beta too. I reran the exact same ts-node test against it and got the identical crash. "Stabilizes the API" means a new, differently-shaped API exists at a new import path. It does not mean require('typescript').createProgram starts working again. Anything written against the classic API has to be rewritten against the new entry points; nothing becomes compatible by just bumping a version number.
Config files break too, in a smaller but more common way. I fed both compilers a set of deprecated tsconfig.json options. Most of them, like target: "ES3" or out, were already hard errors under 6.0.3, just with a different error code. One wasn't:
compilerOptions: { "importsNotUsedAsValues": "remove" }
6.0.3: (accepted silently, no output, exit 0)
7.0.2: error TS5023: Unknown compiler option 'importsNotUsedAsValues'.
importsNotUsedAsValues has been deprecated in favour of verbatimModuleSyntax for several releases, but 6.0.3 still tolerated it quietly. 7.0 doesn't. Anyone upgrading a tsconfig that was last touched two or three TypeScript versions ago should grep it for options that used to be ignorable warnings, because some of them are now a build that won't start.
Under concurrency
The release notes mention new flags for parallel project-reference builds, and tsc --help --all on 7.0.2 lists --singleThreaded, meaning parallel is now the default. I built a small composite project, four independent packages referenced from a root tsconfig.json, each containing 400 generated generic type definitions, enough that the per-project check takes a fraction of a second rather than nothing:
| Mode | Wall time (3 runs) |
|---|---|
| 7.0.2, default (parallel) | 0.75s, 0.79s, 0.83s |
7.0.2, --singleThreaded
|
1.38s, 1.40s, 1.48s |
| 6.0.3 (no parallel option exists) | 4.26s |
Parallelism alone is worth about 1.8x on four independent projects on this four-core machine, separate from the raw speed of the native checker. The two effects stack: the full build goes from 4.26 seconds on the old compiler to under a second on the new one by default, with no flag to set. On a bigger monorepo with more independent project references and more cores, I'd expect that gap to widen rather than narrow, because the whole point of --singleThreaded existing as an escape hatch is that someone will eventually need to turn the parallelism off, not that most people will want to.
Watching it in production
tsc --extendedDiagnostics still works, and the breakdown shows where the time actually went on the NestJS checkout:
6.0.3 7.0.2 7.1.0-beta
Parse time: 1.16s 0.419s 0.291s
Bind time: 0.61s 0.093s 0.093s
Check time: 9.91s 4.603s 2.502s
Total time: 12.21s 5.146s 2.910s
Check time is where almost all of the wall clock goes in every version, and it's also where the native compiler's advantage is largest proportionally, not just absolutely. If you're deciding whether upgrading is worth the breakage above, this is the number to look at in your own --extendedDiagnostics output before and after: a codebase that spends most of its time in parsing rather than checking (lots of files, not much generic-heavy type logic) will see a smaller win than NestJS did here. I'd treat the Symbols and Types counts in that same output with caution, though: they roughly doubled between 6.0.3 and 7.0.2 on an identical checkout, which almost certainly reflects a different internal counting convention in the rewrite rather than the new compiler creating twice as many actual symbols, and I have no way to confirm which from the outside.
What I got wrong on the way
My first run of the headline benchmark gave suspiciously good numbers: all three compilers finished in about a quarter of a second, uniformly, with exactly one error each. I almost wrote that down as "TypeScript 7 is absurdly fast even before you account for anything else," which should have been the first clue, because a result that flattering is usually the harness, not the finding. The one error was TS2688: Cannot find type definition file for 'node', because I'd installed NestJS's dependencies with a plain npm install that hit a peer-dependency conflict and silently produced no node_modules directory at all. Every compiler was failing to even construct the program, immediately, before checking a single file, and reporting an identical fast "success." Reinstalling with --legacy-peer-deps fixed it and pushed every run from a quarter of a second to several seconds, which is when the real differences between the three versions showed up.
Run it yourself
This installs three versions of TypeScript side by side without touching a global install, clones NestJS, and runs the headline comparison:
mkdir -p tsbench/engines && cd tsbench
for v in 6.0.3 7.0.2 7.1.0-beta; do
npm install --prefix "engines/ts_${v//[.-]/_}" --silent "typescript@$v"
done
git clone --depth 1 https://github.com/nestjs/nest.git target/nest
npm install --prefix target/nest --ignore-scripts --legacy-peer-deps --silent
for v in 6_0_3 7_0_2 7_1_0_beta; do
echo "=== $v ==="
TSC="engines/ts_$v/node_modules/.bin/tsc"
time "$TSC" --noEmit -p target/nest/tsconfig.json
done
The --legacy-peer-deps flag matters: without it, npm install can fail outright on a conflicting peer dependency, or in the worse case leave you with no node_modules and every compiler reporting the same hollow "success" I got on my first attempt. Check the file count and error count match across versions before you trust a timing number from this, not just the time itself.
What to do with this
If you're on TypeScript 6 and your build is dominated by type-checking rather than I/O, 7.0 is worth the upgrade on speed alone, even at 2.5x rather than 12x. Before you do it, grep your tsconfig files for options your team hasn't touched in a while, because some of yesterday's warnings are today's build failures. And if your toolchain includes ts-node, ts-jest, or anything doing type-aware linting, check whether it has shipped a release that targets TypeScript 7's new API paths before you bump the version in CI; the error if it hasn't is a flat crash, not a graceful fallback, and 7.1's "stabilized API" doesn't change that for code still written against the old one.
Top comments (1)
the memory number is what I would watch first. 7.0 chewing 65 percent more peak memory is exactly the kind of thing that passes locally and OOMs a small CI runner. I would run the new compiler in parallel in CI for a while before it gates anything.