yanncabral.dev/writing/We Replaced Our Slow TypeScript Checks With tsgo. The Results Were Not Subtle.
Back
Engineering

We Replaced Our Slow TypeScript Checks With tsgo. The Results Were Not Subtle.

YCYann CabralMay 20267 min readtypescript

A practical benchmark from a real monorepo, a painfully slow pre-push hook, and one TypeScript experiment that actually paid off.

There is a specific kind of developer pain that does not show up in architecture diagrams.

It shows up when you finish a change, run your pre-push hook, and then wait. And wait. And wait.

In our case, the hook was taking 289.14 seconds.

That is not "a little slow." That is almost five minutes of context switching pressure. It is long enough to check Slack. Long enough to open another task. Long enough to forget the exact mental model you had when you decided the change was ready.

The worst part? The check was valuable.

We were not wasting time on a useless script. We were validating that all projects in the monorepo were still type-safe before pushing. That is exactly the kind of guardrail I want in a serious TypeScript codebase.

But a guardrail that takes five minutes becomes something developers start negotiating with.

So we tested a simple question:

Can tsgo, the native TypeScript preview, make this fast enough to keep the safety without killing the feedback loop?

Short answer: yes.

Long answer: yes, but only if you treat it like an engineering migration, not a hype-driven package swap.


The Pain: Type Safety Was Right, The Feedback Loop Was Wrong

Our setup was straightforward:

  • Bun monorepo
  • Multiple TypeScript projects
  • Central typecheck script
  • .husky/pre-push calling the typecheck before code leaves the machine
  • CI also validating the same path

Conceptually, this is the right shape.

The problem was execution time.

The initial .husky/pre-push simulation took:

289.14s

That is the kind of number that changes behavior.

When type checks are fast, developers run them often. When they are slow, people batch risk. They wait until the end. They push less frequently. They rely more on CI. They start treating local validation like a tax instead of a feedback mechanism.

And that is where quality quietly gets worse.

I do not want fewer checks. I want checks that are fast enough to become invisible.


The Experiment: Swapping The Engine Without Removing The Seatbelt

The tool we tested was tsgo, shipped through the official @typescript/native-preview package.

Microsoft describes this as the native preview of TypeScript, implemented in Go, with the goal of bringing major speedups to the compiler and tooling. The package exposes a tsgo executable that behaves similarly to tsc for many project-level checks.

Important nuance: this is still a preview, not a blind replacement for every TypeScript workflow. The official TypeScript announcement calls out that some functionality is still incomplete, including areas like build mode and declaration emit. That matters.

So the migration strategy was intentionally conservative.

We did not rip out tsc.

We did this instead:

  1. Installed @typescript/native-preview at the root.
  2. Updated the central scripts/typecheck.ts to use tsgo by default.
  3. Added a --compiler tsc path so the same script can fall back to regular TypeScript.
  4. Added a dedicated typecheck:tsc script.
  5. Left .husky/pre-push unchanged because it already called bun run typecheck.
  6. Updated CI to run the same default typecheck path.
  7. Adjusted TypeScript config compatibility where needed.
  8. Validated both the fast path and the fallback path.

This is the part people skip when they turn benchmarks into tweets.

The win is not "install package, go fast."

The win is making the faster path the default while keeping a boring, reliable escape hatch.


The Compatibility Work

The migration was not completely zero-touch.

We had to make a few compatibility adjustments for the native preview:

  • Removed baseUrl usage where it caused compatibility problems.
  • Kept aliases relative and explicit.
  • Declared types: ["bun"] where Bun runtime types were needed.
  • Added a CSS module declaration for docs client styles.

None of this was dramatic, but it is worth mentioning because it is the difference between a real migration and a demo.

tsgo is fast, but it is also stricter or different in some places because it is part of the next generation of TypeScript tooling. If your project has old module resolution assumptions, custom TypeScript behavior, or build-mode dependencies, you should expect to test carefully.

That is not a reason to avoid it.

It is a reason to benchmark it properly.


The Results

Here are the actual numbers from the experiment.

Initial .husky/pre-push baseline:       289.14s
tsgo via bun run typecheck:               7.02s
Full .husky/pre-push after the change:   14.36s
tsc fallback via typecheck:tsc:          200.68s

The full pre-push hook went from:

289.14s -> 14.36s

That is about:

20.1x faster

Or, said differently:

~95% less waiting time

The direct typecheck comparison was even more aggressive:

200.68s with tsc fallback
7.02s with tsgo

That is about:

28.6x faster

This is not a micro-optimization.

This changes how often a team is willing to run the check.

And that is the real productivity story.


Why This Matters More Than The Raw Speedup

People tend to underestimate how much slow tooling changes engineering culture.

A 5-minute check becomes a negotiation:

  • "I will run it after one more change."
  • "CI will catch it."
  • "This is a small change."
  • "I already touched only one package."

A 14-second check is different.

You just run it.

That means fewer broken pushes, faster local confidence, and less reliance on CI as the first serious validation layer.

The best developer experience improvements are not always new UI, new frameworks, or new abstractions. Sometimes they are the removal of friction from a workflow that already had the right intent.

This was one of those cases.


The Part I Would Not Skip: The Fallback

I would not ship this migration without a tsc fallback.

Not because tsgo failed our checks. It passed.

But because @typescript/native-preview is still a preview, and TypeScript is foundational infrastructure. If the fast path ever diverges in a way that matters, I want the team to have a one-command answer:

bun run typecheck:tsc

That fallback passed too:

200.68s

Slow, yes.

But available.

That changes the risk profile of the migration. We get the speed of tsgo in the default workflow, while preserving the ability to compare against stable TypeScript when needed.

That is the kind of tradeoff I like: fast by default, conservative when debugging.


Validation Before Calling It Done

After the switch, we validated the actual workflow instead of stopping at the happy-path command.

The checks passed:

bun run typecheck
bun run typecheck:tsc
bun run lint:biome
bun run test
.husky/pre-push simulation

That last one matters.

It is easy to benchmark the isolated command and miss the fact that the real developer workflow still has other costs. In our case, even the complete pre-push hook landed at 14.36s, which is still fast enough to preserve flow.

That is the threshold I care about.

Not "is the compiler faster in isolation?"

But "does the developer workflow become meaningfully better?"

Here, the answer was obvious.


So, Was tsgo Worth It?

For this monorepo, yes.

Not theoretically. Measurably.

We reduced the pre-push wait from almost five minutes to under fifteen seconds. We kept the same safety intent. We updated CI. We preserved a tsc fallback. We validated lint, tests, typecheck, fallback typecheck, and the hook itself.

That is a trade I would make again.

My take:

  • If your TypeScript checks are already fast, do not migrate just for novelty.
  • If your repo has a slow local typecheck, tsgo is absolutely worth benchmarking.
  • If you adopt it, keep tsc available as a fallback.
  • If your workflow depends heavily on unsupported preview features, wait or isolate the experiment.
  • If your team is skipping checks because they are too slow, fix the tooling before blaming developer discipline.

The important lesson is not that every project should switch today.

The lesson is that feedback loop speed is an engineering quality issue.

Slow checks do not just waste time. They change behavior. Fast checks make the right behavior easy.

And in this case, tsgo turned a check people could resent into a check people can actually run.

That is the kind of infrastructure improvement that compounds.


References

More posts