Home / Articles

Checking vs. Running: Where TypeScript 7's Go Compiler Fits Your Toolchain

Learn what TypeScript 7's native Go compiler really speeds up, why tools like Angular and typescript-eslint lag behind, and how to split type-checking from execution.

Publisher: REACTAPP.TOP14 min read3257 words

This article is published in English.

Sources: TypeScript 7 Moved Its Compiler to Go. Here’s What Actually Changes

TypeScript 7 replaces the self-hosted compiler with a native port written in Go, and the headline numbers are large enough to cause confusion about what actually gets faster. Your production JavaScript does not change speed at all; your editor, your CI type-check and your monorepo builds do. At the same time, Node, Bun and Vite have made the TypeScript compiler optional for running and transforming code. This guide separates those threads so you can decide what to upgrade, what to wait on, and how to structure a toolchain where checking and executing are two distinct jobs.

TypeScript's reach makes this worth getting right. In August 2025 GitHub reported that TypeScript had become its most-used language by monthly contributors, ahead of both Python and JavaScript, with more than 2.6 million people contributing to TypeScript code each month. A change to the foundation of that ecosystem ripples through frameworks, linters, package managers and editors, and not all of them move at the same pace.

From a self-hosted compiler to a native one

Before version 7, tsc was a bootstrapped program. Its source was TypeScript, it was compiled down to JavaScript, and that JavaScript ran on Node to parse, check and emit everyone else's projects. Self-hosting is a respectable tradition for compilers, and this design carried the language through a decade of explosive growth. Its ceiling, though, is the JavaScript runtime: a single main thread, a garbage-collected heap that was never tuned for giant in-memory graphs of syntax nodes and types, and limited ways to share that data across workers.

Microsoft's answer was a port, not a rewrite. The Go codebase deliberately mirrors the structure and behavior of the existing implementation, so the type system and its semantics are meant to stay the same. What changes is the foundation beneath them: native machine code, threads that can share memory, and far more room to run work in parallel. Go was a pragmatic choice for exactly that reason; its memory model and garbage collector let the team translate the existing code with similar data structures, rather than redesigning everything around ownership rules.

The published benchmarks show typical full builds getting 8x to 12x faster:

  • The VS Code codebase drops from 125.7 seconds on TypeScript 6 to 10.6 seconds on TypeScript 7.
  • Playwright's full build shrinks from 12.8 seconds to 1.47 seconds.
  • Configuring eight type-checker workers pushes the two results down again, to 7.51 seconds (VS Code) and 1.16 seconds (Playwright).

"TypeScript is ten times faster" is an accurate summary of those figures. It is also easy to misread, because it does not say what is ten times faster.

What gets faster, and what does not

Take a small, ordinary piece of typed code: an interface and an object that conforms to it.

interface User {
  id: string;
  name: string;
}
const user: User = {
  id: '42',
  name: 'Arthur',
};

Whatever tool compiles it, the browser or server eventually receives plain JavaScript with the type information removed:

const user = {
  id: '42',
  name: 'Arthur',
};

The runtime has no idea which compiler processed the source, or what language that compiler was implemented in. It only sees the emitted JavaScript, and that output is the same as before. Rewriting tsc in Go therefore does nothing for the execution speed of your application. If a page is slow or an API handler is CPU-bound, TypeScript 7 will not change that.

The gains land earlier, during development, where TypeScript runs constantly: loading projects, computing diagnostics, serving completions and find-all-references, watching files, and checking the build again in CI. One measurement captures this well. Opening a file with an error in the VS Code repository used to take about 17.5 seconds before the first diagnostic appeared; with TypeScript 7 it takes under 1.3 seconds. The new language server is built on the Language Server Protocol, can use multiple threads, and Microsoft also reports far fewer failed language-server commands and crashes.

So the useful framing is this: the application is not faster, but working on the application is. On a large codebase or monorepo, shaving minutes off every type-check and seconds off every editor diagnostic changes the feedback loop you live in all day. That is a real productivity gain even though no user will ever notice it.

The rest of the TypeScript 7 release

The native compiler dominates the conversation, but several subsystems were rebuilt around it.

Parallelism you can tune

Parsing, emitting and parts of type checking can run in parallel, and project-reference builds can compile multiple projects at once, which matters most in monorepos. You can raise the number of type-checking workers explicitly:

tsc --checkers 8

Or turn parallelism off entirely, for machines with tight resource limits or when you want deterministic behavior while debugging:

tsc --singleThreaded

A separate --builders flag controls how many project-reference builds run concurrently. Treat these as tuning knobs rather than "max everything" switches. Every extra worker holds its own share of the program in memory, so a CI runner with many cores but modest RAM can end up slower, or run out of memory, if you simply match workers to cores. Measure on your real runners before committing a setting.

A rebuilt watch mode

--watch was also reimplemented. The team first tried more conventional cross-platform approaches available in Go, but polling the file system became too expensive for large projects. They ended up porting ideas from Parcel's C++ file watcher into Go and built a new watcher aimed at better resource usage across operating systems.

New defaults and removed legacy options

TypeScript 7 carries forward the configuration changes introduced during the TypeScript 6 transition:

  • strict is enabled by default.
  • The default for module is now esnext.
  • types starts out as an empty array, so global type packages are no longer picked up automatically.
  • target: es5 has been removed.
  • Classic module resolution has been removed.
  • AMD and UMD module output have been removed.
  • baseUrl is no longer supported.

A tsconfig.json that has not been touched since Internet Explorer was a deployment concern will likely need attention. The types change in particular can surface as a wall of "cannot find name" errors for test globals or Node built-ins until you list the packages you rely on. If you have not yet worked through these defaults, our practical migration guide to TypeScript 6.0's breaking defaults covers them in detail, and the same fixes apply here.

The missing piece: a stable compiler API

The most consequential detail is something TypeScript 7.0 does not include: a stable programmatic API. For many projects that matters more than any flag or default, because it decides whether the tools around the compiler can move at all.

Why the ecosystem cannot switch overnight

Invoking the compiler from a terminal and building a tool on top of the compiler are very different relationships. Plenty of frameworks, language tools and linters do not just shell out to the binary:

tsc

They load TypeScript as a library and ask it for syntax trees, resolved types, language-service answers and other compiler internals. The replacement for that API is expected in TypeScript 7.1, and until then Microsoft officially supports installing both major versions in parallel. At the time of writing, that means many teams will use the native compiler for command-line checks while their editor integrations and linters keep loading the JavaScript-based version.

Several familiar tools are affected:

  • Angular's template type checking is deeply integrated with TypeScript, so Angular's complete editor support has to stay on the older language service for now.
  • Tooling for Vue, Astro, Svelte, MDX and other formats that embed TypeScript in another file type faces similar limits until the new API ships.

None of this is a flaw in the port. It is what a migration of a foundational tool looks like in a real ecosystem: the engine is ready before everything bolted onto it has been adapted.

Four ways a tool can depend on TypeScript

typescript-eslint is a good lens for this. ESLint itself is not built on the TypeScript compiler, so claiming that "ESLint got faster because TypeScript moved to Go" would simply be wrong. typescript-eslint, however, is the bridge that lets ESLint parse TypeScript syntax and, for type-aware rules, query TypeScript's type information. Its current parser still relies on the older compiler API, even though the project has been experimenting with the native implementation. The maintainers are clearly preparing, but adopting TypeScript 7 there is not a matter of bumping a version number.

That example exposes a distinction worth keeping in mind whenever someone says a tool "uses TypeScript". There are at least four different relationships:

  • Written in TypeScript. The tool's own source is TypeScript. It benefits when its maintainers type-check and build faster, but its users see no change.
  • Executes TypeScript. Runtimes like Node and Bun accept .ts files as input without involving tsc.
  • Transforms TypeScript. Bundlers and transpilers remove types and rewrite syntax, one file at a time.
  • Depends on TypeScript's semantics. Linters with type-aware rules, framework language services and editors need the compiler to understand a whole program.

The native port changes each category differently. Only the last one needs the compiler API, and it is exactly the category that has to wait for 7.1.

A package manager caught mid-migration

pnpm shows how these categories interact. It is a large TypeScript monorepo with many packages, heavy CI and plenty of editor analysis, which on paper makes it an ideal beneficiary of TypeScript 7. Eventually it should be. In the meantime, its maintainers must also keep typescript-eslint working, and that linter integration is still tied to the legacy compiler interface. The dependency chain looks roughly like this:

pnpm
  ↓
typescript-eslint
  ↓
TypeScript compiler API
  ↓
TypeScript 6 — for now

It is also worth being clear about what users of pnpm will experience. TypeScript 7 does not make this command any faster:

pnpm install

Installing packages is pnpm's runtime behavior, and pnpm runs as JavaScript on Node regardless of which compiler checked its source. What TypeScript 7 can improve is the experience of developing pnpm: type-checking, CI runs and other compiler-driven workflows. And even that improvement arrives only as fast as the slowest important link in its dependency graph. A large project cannot adopt a foundational change faster than the tools it depends on.

Where the gains are immediate

Projects that only need the compiler as a command-line checker and a language server can benefit right away. VS Code is the clearest case: a huge TypeScript codebase that is also where millions of developers experience TypeScript tooling every day. Cutting a full compile from over two minutes to roughly ten seconds is significant; cutting the wait for the first editor diagnostic from about seventeen seconds to just over one is something anyone can feel.

Playwright, one of Microsoft's benchmark projects, drops from 12.8 seconds to 1.47 seconds for a full compile with the default configuration. Again, that does not mean Playwright starts Chromium any faster. It means maintaining and checking Playwright's own code becomes much quicker. Given how much developer tooling is itself written in TypeScript, faster maintenance of those tools compounds across the ecosystem.

Vite already treats checking and transforming as separate jobs

Vite is predominantly written in TypeScript, but when it serves or builds your TypeScript application it does not call the TypeScript compiler to transpile each module. Modern Vite runs a dedicated transformation pipeline and intentionally skips type checking. Its documentation recommends running TypeScript separately, as a one-off check:

tsc --noEmit

or continuously during development:

tsc --noEmit --watch

The reasoning is sound. Transforming a file only requires that file: strip the annotations, rewrite a few constructs, move on. Type checking requires knowledge of the entire module graph, because a change in one file can break a contract in another. Doing both in the same step forces the fast operation to wait on the slow one. A typical Vite project therefore has two parallel paths from the same source:

┌── Vite / Oxc ── transform ──> Browser
TypeScript source ──┤
                    └── TypeScript ───── type-check

Seen through this lens, TypeScript 7 does not need to become the fastest JavaScript transformer, because Vite already has a tool optimized for that. TypeScript can focus on the job that requires understanding the whole program, and that job is now dramatically faster. If you use this setup today, one practical consequence is that a type error does not stop the dev server; make sure tsc --noEmit runs in CI or a pre-push hook so errors cannot slip through.

JavaScript tooling written in everything except JavaScript

There is a broader trend here. Vite's current build pipeline uses Rolldown, and both Rolldown and Oxc are written in Rust. esbuild chose Go years ago. Bun implements large parts of itself in Zig. Now TypeScript has joined them with Go.

That is not a criticism of JavaScript. The public interfaces still feel natural to JavaScript and TypeScript developers, while the CPU-heavy parsing, resolution and analysis underneath run as native code. Different layers have different requirements, and TypeScript 7 is the most prominent recent example of choosing the implementation language per layer.

Runtimes that run TypeScript without tsc

The same split is happening on the runtime side. The traditional mental model of a TypeScript project was a straight line from source to compiler to JavaScript to Node:

TypeScript
    ↓
   tsc
    ↓
JavaScript
    ↓
  Node.js

Real projects always had more steps, but the core idea held: Node ran JavaScript, so TypeScript had to be turned into JavaScript first. Today this works directly:

node server.ts

The important detail is that Node is not running the TypeScript compiler. Its built-in support is intentionally minimal: it strips erasable TypeScript syntax and executes what remains. It does no type checking, and syntax that requires generating actual JavaScript (rather than simply deleting annotations) falls outside that stripping model. A typed declaration such as this one:

const port: number = 3000;

effectively becomes the following before execution:

const port = 3000;

"Erasable" is the key word. Constructs like enum, namespace with runtime values, or constructor parameter properties produce JavaScript that did not exist in the source, so pure stripping cannot handle them. TypeScript offers the erasableSyntaxOnly compiler option to flag such constructs if you want your code to stay compatible with type stripping; check the current Node and TypeScript documentation for the exact behavior in your versions.

This capability also predates Node 26, so direct TypeScript execution should not be presented as a Node 26 feature. Node 26 brings its own changes, including Temporal enabled by default, a newer V8, another major Undici update and the usual cleanup of a major release, but those are a separate topic. What matters for this discussion is the direction: Node increasingly treats TypeScript as input it can consume directly, without first asking tsc to emit JavaScript.

Bun treats TypeScript as normal input

Bun, still on its 1.x line, has worked this way from early on. Running a TypeScript file is just:

bun server.ts

Bun's loader strips and transforms TypeScript syntax before execution using its own tooling. And, once more, transpiling is not type checking. A perfectly reasonable Bun workflow is therefore two commands:

tsc --noEmit
bun server.ts

The first answers the question "does this program satisfy the type system?" The second answers "can this program run?" Modern Node supports the same separation:

tsc --noEmit
node server.ts

In both cases, the runtime never waits for the type checker, and the type checker never needs to produce output files. Keep in mind that a project run this way has no build step to catch type errors by accident; if the check is not wired into CI, it will simply not happen.

TypeScript's job is narrowing to understanding programs

For years the workflow compressed into a single pipeline, where the compiler sat squarely between writing and running code:

Write TypeScript
      ↓
Compile TypeScript
      ↓
Run JavaScript

Modern tooling increasingly looks like two branches from the same source, one for understanding and checking, the other for transforming and executing:

┌── TypeScript ── understand and check
TypeScript source ────────┤
                          └── Runtime / bundler ── transform and execute

Vite can use Oxc to transform code while tsc checks it. Node strips erasable syntax on its own. Bun has its own loader. None of these tools need TypeScript 7, or any tsc, to execute your application. That is precisely why the native compiler still matters: TypeScript's most distinctive responsibility is no longer turning a .ts file into a .js file quickly, which other tools already do well. Its real value lies in whole-program questions like these:

  • Is this value compatible with the contract it is passed into?
  • Has a refactor broken a consumer in another package?
  • Is this generic constraint correct?
  • What does this symbol refer to, and where else is this type used?
  • What should the editor offer as a completion right here?

Answering those needs far more context than deleting : string from a line, and that whole-program analysis is the part the Go port makes dramatically faster. For a deeper look at running TypeScript natively across these tools, see our migration guide to TypeScript's Go compiler and native execution.

Key takeaways

  • TypeScript 7 is a port of the existing compiler to Go, not a redesign of the language; the emitted JavaScript and your application's runtime performance are unchanged.
  • The wins are in development: full builds typically 8x to 12x faster, much quicker editor diagnostics, and a more stable language server.
  • Tune --checkers, --builders and --singleThreaded against real CI memory limits instead of maximizing workers blindly.
  • Expect a hybrid period. Without a stable compiler API until 7.1, Angular, Vue, Svelte, Astro, MDX tooling and typescript-eslint may keep you on TypeScript 6 for some integrations, and running both versions side by side is supported.
  • Audit tsconfig.json for the new defaults and removed options (strict, module, types, ES5 target, classic resolution, AMD/UMD, baseUrl).
  • Treat type checking as its own step. Vite, Node and Bun execute or transform TypeScript without checking it, so make tsc --noEmit a required part of CI.