We Upgraded to TypeScript 7 and the Build Got Weird

The native Go compiler promised 10x faster builds. We got that, plus a week of chasing down every tool in our pipeline that assumed tsc was slow.


The pitch was simple. TypeScript 7's native compiler, rewritten in Go, delivers roughly 10x faster type-checking and builds. Our client's monorepo took 4 minutes and 12 seconds to type-check. The math was appealing.

What nobody mentioned is that half a decade of tooling had been built around the assumption that TypeScript compilation is slow. When you remove that assumption, things get weird.

The upgrade itself was boring

I'll give Microsoft credit here. The actual compiler upgrade on a 380k-line monorepo was uneventful. We bumped typescript from 5.7 to 7.0, ran tsc --build, and got zero new type errors. The Go port is genuinely faithful to the JavaScript implementation — same type system, same inference behavior, same errors. We'd been burned enough by "drop-in replacement" claims to be skeptical, but this one held up.

Full project build went from 4m12s to 26 seconds. On developer machines with warm OS caches, incremental builds dropped to under 2 seconds. The numbers were real.

The problems started about an hour later.

The CI pipeline that optimized for slowness

Our CI had a carefully orchestrated parallel build strategy. Type-checking, linting, unit tests, and integration tests all ran concurrently because type-checking used to be the bottleneck. The pipeline was shaped like this:

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - run: npx tsc --build --noEmit
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: npx biome check .
  test-unit:
    runs-on: ubuntu-latest
    steps:
      - run: vitest run --project unit
  test-integration:
    needs: [typecheck]
    runs-on: ubuntu-latest
    steps:
      - run: vitest run --project integration

Four parallel jobs, each spinning up its own runner, installing dependencies, restoring caches. That orchestration overhead was about 90 seconds per job. When tsc took 4 minutes, it made sense — you were hiding that 90 seconds behind the long compile. Now tsc finished in 26 seconds, and we were spending 90 seconds of setup to save... nothing. The parallel strategy was slower than running everything sequentially on a single runner.

We collapsed it into one job. Total CI time dropped from 6 minutes to 3 minutes and 40 seconds. Not because TypeScript got faster — we'd already gotten that win — but because we stopped paying the parallelism tax on work that no longer needed it.

The file watcher that couldn't keep up

This one was genuinely confusing. After the upgrade, developers started reporting that tsc --watch would occasionally miss changes. You'd save a file, wait, and nothing would happen. Save again, and it'd pick up both changes.

The old compiler was slow enough that by the time it finished a compilation cycle, any new file-system events had already been queued by the OS. The native compiler finishes so fast that it was completing its cycle and re-registering its file watchers before the OS had flushed all the inotify events from the buffer. On Linux specifically, with the default inotify watcher, there was a narrow race condition.

The fix was one line in tsconfig.json:

{
  "watchOptions": {
    "watchFile": "useFsEvents"
  }
}

Switching to fs.watch instead of inotify polling resolved it. But I'd never have guessed "the compiler is too fast for the file watcher" as a failure mode.

The editor plugin that cached too aggressively

The team used a homegrown VS Code extension that displayed type information in a sidebar panel. It shelled out to tsc to get diagnostics and cached results for 5 seconds — a reasonable interval when compilation took a few seconds anyway.

With the native compiler, developers would fix a type error, and the sidebar would still show the error for up to 5 seconds. Technically it always had that staleness window, but when tsc itself took 3 seconds, a 5-second cache meant you were at most 2 seconds behind. Now that tsc returned in 200 milliseconds, the cache was the bottleneck. We dropped it to 500ms and the complaints stopped.

The build cache we no longer needed

The team had invested two sprints — about three weeks of actual work — building a remote build cache for TypeScript compilation artifacts. It used content-addressable storage backed by S3, with a local LRU cache and a custom Turborepo plugin. Clever engineering. Genuinely well-built.

With the native compiler, a full cold build from scratch took 26 seconds. Restoring the cache, verifying checksums, and resolving cache entries took about 18 seconds. The cache saved 8 seconds on a hit and cost 18 seconds on a miss.

We ripped it out. Three weeks of work, deleted in an afternoon. The tech lead took it well, to his credit. "I always said it was a workaround," he told me. He wasn't wrong.

Note

If you have custom build caching for TypeScript specifically, benchmark the native compiler without the cache first. You might find the cache infrastructure costs more time than it saves.

What I actually learned

The TypeScript 7 upgrade story isn't about TypeScript. It's about how we build layers of complexity to work around performance problems, and those layers become load-bearing. When the underlying problem gets fixed, the workarounds don't gracefully disappear. They become the new problems.

Every caching layer, every parallelization strategy, every "we'll batch this because it's slow" decision creates an assumption. Fast enough isn't just about the tool getting faster — it's about all the scaffolding you built around the slowness.

I've seen the same pattern with database query optimization, CDN caching, and API response batching. You solve a performance problem, and five auxiliary systems that were compensating for that problem suddenly become unnecessary overhead. But nobody removes them, because nobody remembers they were workarounds.

The TypeScript 7 native compiler is good. Genuinely, unreservedly good. But if your build pipeline is anything like ours was, the upgrade is the easy part. The interesting work is finding all the places your tooling quietly assumed that compilation would be slow — and deciding which of those assumptions you're ready to throw away.