I fixed one component and hit save. All I'd changed was a single line of types, yet CI's tsc --noEmit churned away for ages again. Anyone who has built a large frontend in TypeScript knows the scene. "Type checking is slow" isn't an abstract complaint — it's a wait that gets a little longer every day as your app grows.
In 2026, an answer to that wait finally arrived. Microsoft rewrote the entire TypeScript compiler in Go as a native version, codenamed Corsa, with the executable named tsgo. The headline of the official announcement was "10x faster." So I checked it for myself. I dropped tsgo straight onto the var.gg frontend monorepo that powers this very blog — a Next.js App Router project with 700-some .ts/.tsx files — and ran it side by side with the tsc we already use.
To put the conclusion up front: yes, it got faster. But "10x" wasn't what we saw in our code, and the more interesting finding wasn't the speed at all — it was that the two compilers did not report the same errors.
TL;DR
-
tsgois not "a tool that compiles your TypeScript into native code." It's the TypeScript compiler itself, moved from a self-hosted JavaScript implementation to a native Go implementation. What it produces (type judgments,.js,.d.ts) is the same; the engine that produces it has changed. - Measured on our monorepo (728 files,
skipLibCheckon): a full type check went fromtsc6.7s →tsgo2.1s, about 3.2x. The official "10x" comes from large codebases like VS Code with 1.5 million lines. When a project is small or already optimized, the multiplier shrinks. - The real discovery wasn't speed but a diagnostics mismatch.
tsgo(the 7.0 dev preview) caught one overload error in our code thattsc6.0.3 let through. The first thing to look at in a migration isn't "how fast is it" but "does it give the same verdict." - As of June 2026, TypeScript 7.0 is in Beta/preview. The stable line is 6.0.x, and
tsgois a dev build shipped as@typescript/native-preview. The right strategy right now isn't to replacetsc, but to runtsgoside by side as a sidecar that measures speed and diagnostics.
One-line glossary — compiler: a program that transforms and checks human-written code so machines or other formats can work with it. TypeScript's
tscdoes less transforming and more "checking whether the types are correct."
The paradox of TypeScript being written in TypeScript
Until now, tsc was written in TypeScript and ran on JavaScript. Building your own compiler in your own language is called self-hosting. It wasn't a bad choice. The team got to use their own language every day (so-called dogfooding) and grow the feature set quickly.
The problem was scale. Roughly speaking, here's what the compiler does.
- scanner/parser: chops the source — a blob of characters — into tokens and turns it into an AST (Abstract Syntax Tree, a tree-structured representation of source code that's easier for the compiler to work with).
-
binder: connects a name like
footo the declaration it refers to. -
type checker: works out whether the types you wrote contradict the actual values, calls, and inheritance. Calling
.toUpperCase()on anumbergets caught here. -
emit: from the check results, emits
.jsor.d.ts(a "contract" file that holds only type information, with no JavaScript implementation). - language service: a separate service responsible for the editor's autocomplete, red squiggles, rename, and go-to-definition. It uses the same information as the build.
All of this runs sequentially on a single JavaScript runtime (a single thread). As the app grows, you have to hold the AST and the type graph in memory, and the cost of GC (garbage collection) that clears away unneeded objects grows too. By analogy, it's like one skilled librarian classifying every book in a large library alone, one at a time. No matter how good the librarian is, when there are a million books the line gets long.
Why Go and not Rust — "porting," not "rewriting"
Here's a common misconception. "If they wanted performance, they should have written it in Rust — why Go?" The official FAQ's answer isn't a matter of taste. The key is the difference between port and rewrite.
The TypeScript team did not redesign the compiler. They moved it to Go while preserving the behavior and code structure of the existing compiler as much as possible. Why does that matter? Because the entire ecosystem depends on the TypeScript type system down to subtle behaviors written in no spec (so-called Hyrum's Law — with enough users, every behavior of your system, even ones not in the spec, will be depended on by somebody). The moment you redesign, the risk that those subtle behaviors drift goes way up.
So "how easy is it to port the existing code structure one-to-one" was the top criterion for the language choice. Go is structurally similar to the patterns of the existing TypeScript code, gives enough control over memory layout, and suits tree/graph processing. Rust wasn't eliminated for being slow; more precisely, redesigning around Rust's ownership and memory model would make it harder to guarantee "the same TypeScript." Because compatibility came first, they chose a port over a rewrite.
So, to stress it again — tsgo does not turn your TypeScript into Go or a native binary. What changed is the language the compiler is built in, not the output the compiler produces. Back to the library analogy: the classification rules stay the same; the change is to let several librarians split the work across sections while sharing one catalog.
TypeScript 6.0 as a bridge
To talk about tsgo, you have to start with TypeScript 6.0. 6.0 is a peculiar release. The official announcement describes it as "the last major release for the existing JavaScript codebase" and a "bridge to crossing over to 7.0's Go native." In fact, there are no plans to make TypeScript 6.1, and the 6.0 line will only receive patches focused on security, severe regressions, and 6/7 compatibility issues.
What 6.0 does is "clean up the present for the future." It changes defaults (e.g. strict, types: []) and marks old options as deprecated. And it gives you a safety pin to silence those warnings temporarily: "ignoreDeprecations": "6.0".
But don't mistake this safety pin for a migration strategy. In TypeScript 7, those deprecated options aren't supported at all. That means the list of warnings you hid in 6.0 is exactly the list of hard-error candidates in 7.0.
Here we had a small bit of luck. The var.gg frontend is already on typescript ^6.0.3 — that is, we're standing on the bridge. That's a much further-along starting line than projects still on 5.x. Here are some representative items worth checking ahead of time on 6.x.
| Item | Change in 6.0 | 7.0 perspective | Checkpoint |
|---|---|---|---|
strict |
default true
|
strict worldview in 7 too | little impact if already strict |
types |
default []
|
implicit @types/* inclusion reduced |
declare global types like node, jest, playwright explicitly |
baseUrl |
deprecated | slated for removal in 7 | check whether paths aliases lean on baseUrl
|
moduleResolution: node10 |
deprecated |
nodenext/bundler recommended |
for bundler apps, is bundler the natural fit? |
target: es5 |
up for cleanup | removed | is backward compat handled at the bundler layer rather than in TS? |
Interestingly, when I dug through our code I found that some scripts already had ignoreDeprecations: "6.0" baked into their compile options. A small sign that standing on the bridge doesn't mean "ready to go."
I ran it on our own monorepo
Now the heart of it. Instead of re-citing the official benchmark table, I measured directly in our code.
-
Target:
apps/frontend(Next.js App Router, 728.ts/.tsxfiles undersrc) -
tsconfig:
noEmit: true,incremental: true,skipLibCheck: true,strict: true -
Comparison:
tsc6.0.3 vstsgo(@typescript/native-preview, version7.0.0-dev.20260616.1) -
Command:
--noEmiton both sides (pure type-check time) - Environment: Windows 11, Node v24.15.0
-
cold = first run after removing the incremental cache (
tsconfig.tsbuildinfo), warm = a re-run with the cache present
Installation is a one-liner. Anyone can reproduce the exact same thing in their own project.
npm i -D @typescript/native-preview@beta
# the executable is named tsgo
npx tsgo -p apps/frontend/tsconfig.json --noEmit
Speed
| Compiler | Version | cold | warm |
|---|---|---|---|
tsc |
6.0.3 (JS self-hosted) | 6.7s | 1.9s |
tsgo |
7.0.0-dev (Go native) | 2.1s | 0.7s |
| speedup | ≈ 3.2× | ≈ 2.7× |
It got faster. When a cold 6.7s drops to 2.1s, that's a difference you feel on every PR in a CI environment with no cache. But it wasn't 10x. And that's normal — I'll explain why below.
And the two compilers did not report the same errors
What really made me sit up wasn't the speed.
| Compiler | Error count | Details |
|---|---|---|
tsc 6.0.3 |
3 | all stale references in Next.js-generated .next/types/** (TS2307) — not source bugs |
tsgo 7.0-dev |
4 | same 3 above + 1 additional ↓ |
The one line only tsgo flagged:
src/features/abbreviations/hooks/useInfiniteAbbreviations.ts(82,9):
error TS2769: No overload matches this call.
In useInfiniteQuery({ … initialData: transformedInitialData … }), overload resolution (the several call forms of one function) went different ways. tsgo rejected it; tsc 6.0.3 let it through. Same code, different verdict.
Here I have to be honest. tsgo is the 7.0 dev preview and tsc is the 6.0.3 stable release — the type checker versions themselves differ. So whether this difference is (a) a check 7.0 intentionally strengthened, (b) a preview bug, or (c) a 6.0→7.0 difference in overload selection, I can't say for sure right now. But the conclusion is clear: the preview's diagnostics don't yet match tsc 100%. And this isn't a drawback — it's the very first thing someone preparing for a migration needs to know.
Where does 10x come from, and why was it 3x for us
The official "10x" isn't hype. Its source is clear. In the 2025 A 10x Faster TypeScript announcement, Microsoft published a table measuring real open-source projects.
| Project | LOC | existing tsc
|
native | ratio |
|---|---|---|---|---|
| VS Code | 1,505,000 | 77.8s | 7.5s | 10.4× |
| Playwright | 356,000 | 11.1s | 1.1s | 10.1× |
| TypeORM | 270,000 | 17.5s | 1.3s | 13.5× |
| date-fns | 104,000 | 6.5s | 0.7s | 9.5× |
You can see what they have in common. They're all hundreds of thousands to a million lines — code where type checking is genuinely heavy. By that standard our apps/frontend is on the small side, and on top of that it skips library type checking via skipLibCheck, so tsc's original cost was already low. The conditions for a smaller multiplier are roughly these:
- when the project is small, or more bound by file reading (I/O)
- when it's already optimized with
skipLibCheckand explicittypes - when the CI machine has few cores, so
tsgo's parallelism is less exercised - the fact that
tsgo --noEmitis only a type check and doesn't replace the entirenext build
So there are two sentences this post has to keep separate: "There is an official 10x" (a fact with evidence) and "we got 10x in our code" (a claim you're not allowed to make until you measure it yourself). Our honest answer is ~3x. It may look small, but in a cacheless CI, 6.7s becoming 2.1s is a clear gain the more it repeats.
The speed comes from two things. One is the efficiency of Go native itself; the other is parallelization. The 7.0 Beta runs parsing, type checking, and emit in parallel, tuning the number of type-checking workers with --checkers and parallel project builds with --builders (there's also --singleThreaded for debugging). It's like opening several checkout lanes. But type checking isn't "ring up this customer's items and you're done" — it's entangled with other files, libraries, and generic inference, so parallelization isn't simply splitting files N ways; it's the problem of dividing the work while controlling shared knowledge and ordering dependencies.
What tsgo doesn't give you yet
Don't take the word preview lightly. Looking at the current support table for microsoft/typescript-go, parsing, type checking, build mode, and incremental builds are mostly marked "Done," but there are still blanks.
- watch mode: still a prototype. It can watch for file changes, but incremental re-checking isn't optimized.
- Compiler API: not ready. A stable API for calling the compiler's internals from code is signaled for 7.1 or later at the earliest. This is the core risk for the ecosystem.
-
JS/JSDoc,
.d.tsemit from.js: there are areas where behavior intentionally differs.
The Compiler API in particular is a quiet landmine. The following tools read TypeScript's internal API directly, so they don't automatically come along just because you switch tsc.
| Category | Why it's risky |
|---|---|
typescript-eslint (type-aware lint) |
reads the TypeScript API for type information |
ts-node, ts-jest
|
import the typescript package at runtime |
custom transformers (ts-patch, etc.) |
depend on the existing AST/transform API |
| framework plugins like Vue/Angular | use the language service plugin / compiler API |
Microsoft acknowledges this too, providing separate compatibility packages (@typescript/typescript6, tsc6) for existing API consumers. In other words, they've officially admitted that "the compiler CLI gets faster, but the ecosystem that leans on the API needs a separate path."
And not every fast tool does the same job. Let me lay out the boundaries with esbuild, SWC, and Biome — the ones people commonly confuse here.
| Tool | Type checking | Speed | Suited for |
|---|---|---|---|
tsc 6.x |
full TypeScript type checking | baseline | current production baseline |
tsgo / TS7 |
aims for full type checking | ~10x on large code | fast type checking, validating the future transition |
| esbuild | none (treats types like comments) | very fast | bundling/transpiling |
| SWC | none (per-file transform) | very fast | fast transforms in build pipelines like Next |
| Biome | none (formatter/linter) | very fast | partial replacement for Prettier/ESLint |
The key distinction is this. esbuild, SWC, and Biome can speed up the TypeScript "development experience," but they are not type checkers that judge the whole type system the way tsc and tsgo do. The moment you believe a fast transpiler stands in for type checking, bugs leak out. To actually trust your types, you ultimately need tsc or tsgo. This question of "how far you can tame nondeterminism with tooling" is one I touched once on the Python side in a post on type-safe agents — different language, same grain of worry.
So what to do now
Putting it together, the right answer in June 2026 isn't "ditch tsc and switch to tsgo" but "run the two side by side." Here's the decision flow as a diagram.
📊 The diagram for this section renders in the original article on var.gg →
In words, here it is.
-
First clean up the warnings you hid with
ignoreDeprecationsand the deprecated options. The list you hid is the list of errors in 7.0. - Add
@typescript/native-preview@betaas a devDependency. - Measure
tsc --noEmitandtsgo --noEmitrepeatedly, 7+ times. Look at the diagnostics diff before the average time. - If the diff is 0, add
tsgoas a non-blocking CI job to keep collecting speed and diagnostics. It's safe because a failure won't block the build. - Until the 7.0 stable release and API stabilization, keep
typescript-eslint, the test runner, and custom transformers on the existing TS6 API path.
We finished steps 1–3 with this round of measurement, and because the diagnostics diff wasn't 0 (the TS2769 above), we didn't jump straight to step 4 — we're looking into the cause of that one line first. This doesn't mean "the slower tool is smarter" — it's just the obvious procedure of confirming you get the same answer before switching to a faster tool. The habit of adding one more layer of verification in an era when tools are changing also chimes with what I said in a post on what git tools should become in the agent era.
Conclusion — not a swap, but being ready to measure for yourself
The TypeScript 7 native compiler is an uncommon kind of event. It's a transplant at the heart of the ecosystem — a language moving a compiler that had been written in itself over to another language, straining not to break compatibility. The speed improvement is no exaggeration even going by the official numbers alone, and there's a good chance there's a real gain for a TS monorepo like ours too.
But wholesale-replacing the typescript package right now and pushing even the API-leaning tools across all at once is premature. The most balanced stance is this — on top of TS 6.0.3, clean up the deprecated options, run tsgo --noEmit side by side, and measure diagnostic equivalence and speed for yourself. What that one round of measurement on our monorepo told us was two things: the honest number "about 3x at our scale," and the more valuable warning that "the preview doesn't give the same errors yet."
Being fast isn't the finish line but the starting line. First confirm you get the same answer, then enjoy the speed you've gained. And that confirmation is something anyone can start on their own code today, with the single line npx tsgo --noEmit.
References: TypeScript 7.0 Beta official announcement · A 10x Faster TypeScript · microsoft/typescript-go repository · benchmarks measured directly on var.gg apps/frontend on 2026-06-16.
Top comments (1)
The diagnostics mismatch is super interesting; did you