Speed Up TypeScript Feedback Loops

Cut TypeScript compile times with incremental builds, project references, skipLibCheck, and smart tool choices. Get faster feedback without sacrificing type safety.

6 min read

The TypeScript compiler is thorough, and that thoroughness takes time. As your project grows, you notice: tsc takes two seconds, then five, then fifteen. That gap between saving a file and seeing the result is your feedback loop, and every second the compiler spends is a second you are not writing code.

You can speed up TypeScript without sacrificing type safety. The compiler has built-in features specifically designed to make recompilation faster, and picking the right runtime for development closes the rest of the gap.

Fast vs slow TypeScript feedback loop

When incremental mode is off, the compiler rechecks every file in your project on every save. When it is on, only changed files and their direct dependents are rechecked. The difference compounds as your project grows.

Enable Incremental Builds

The single most impactful option for faster rebuilds is incremental. Add it to your tsconfig.json:

jsonjson
{
  "compilerOptions": {
    "incremental": true
  }
}

With incremental enabled, tsc writes a .tsbuildinfo file to track what was compiled and when. On the next run, it reads that file and skips any file whose source has not changed since the last build. A project with 500 files where you changed one function recompiles in under a second instead of ten.

The .tsbuildinfo file is automatically generated and should be added to .gitignore. It is a binary cache, not source code. Each developer generates their own locally.

Skip Library Type Checks

A significant portion of compile time is spent checking type declaration files from node_modules. These are the .d.ts files shipped by libraries like React, Express, or Lodash. You did not write them, you cannot fix them, and bugs in them are rare.

Skip them with skipLibCheck:

jsonjson
{
  "compilerOptions": {
    "incremental": true,
    "skipLibCheck": true
  }
}

This tells tsc to skip type checking all .d.ts files. Your own .ts files are still fully checked. Errors in library type declarations are almost never actionable, so skipping them saves time with no practical downside.

For more on how declaration files work, see Declaration Files in TypeScript. For avoiding common setup mistakes, see Common TypeScript Tooling Mistakes.

Use Project References for Monorepos

If your project is split into multiple packages, a single tsconfig.json forces tsc to recheck everything when any file changes. Project references solve this by letting each package have its own tsconfig that only checks its own files.

A root tsconfig.json references individual package configs. Start with the root that lists every package:

jsonjson
{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/server" }
  ],
  "files": []
}

Each referenced package needs composite enabled in its own tsconfig. This tells tsc that the package can be built independently and that other packages only need its type declarations, not its full source. Here is the per-package config:

jsonjson
{
  "compilerOptions": {
    "composite": true
  }
}

Composite projects will not build correctly with a plain tsc invocation. Build them with the --build flag instead, which understands the reference graph:

bashbash
tsc --build

The --build flag reads the references tree, checks which packages are out of date, and only rebuilds those. Packages that have not changed are skipped entirely. In a monorepo with ten packages where you changed only the server, tsc --build recompiles the server and any package that imports from it, then stops.

Separate Type Checking from Execution

During development, you do not need type checking on every restart of your dev server. You need fast restarts. Type checking can run separately in your editor or in CI.

Use tsx for development scripts and tsc --noEmit for type checking:

jsonjson
{
  "scripts": {
    "dev": "tsx --watch src/server.ts",
    "typecheck": "tsc --noEmit"
  }
}

tsx strips types with esbuild and starts in milliseconds. Your dev server restarts instantly on file changes. Meanwhile, your editor shows type errors inline through the TypeScript language server.

You get fast restarts during development and full type safety in CI where it matters. For choosing between tsx and other runners, see tsx vs ts-node for Running TypeScript.

Use IsolatedModules for Per-File Speed

The isolatedModules option tells tsc that each file can be transpiled independently without knowing about other files. This is already the default if verbatimModuleSyntax is on, and it is required by most bundlers:

jsonjson
{
  "compilerOptions": {
    "isolatedModules": true
  }
}

With isolatedModules, tools like esbuild and swc can transpile individual files without loading the entire project. This matters because tsx, Vitest, and Vite all rely on per-file transpilation for speed. Without this option, those tools may need to fall back to slower strategies.

Profile Your Build

If compile times are still too slow after these changes, profile to find the bottleneck. Run tsc with the extendedDiagnostics flag:

bashbash
tsc --noEmit --extendedDiagnostics

This prints a breakdown of where the compiler spent its time: how long parsing took, how long type checking took, how many files were processed, and how much memory was used. The output helps you decide whether the bottleneck is file count, complex types, or declaration file checking.

Common culprits include deeply recursive generic types, large union types with hundreds of members, and projects that include every file in node_modules through overly broad include patterns. Tighten your include array to only the directories that contain your source code.

Rune AI

Rune AI

Key Insights

  • Enable incremental in tsconfig.json to store build information and skip unchanged files on rebuild.
  • Set skipLibCheck to true to avoid type-checking library .d.ts files you cannot fix.
  • Use project references and tsc --build for monorepos to only rebuild changed packages.
  • Use tsx for dev scripts: it strips types with esbuild and starts in milliseconds.
  • Run type checking separately with tsc --noEmit in CI rather than during every dev restart.
RunePowered by Rune AI

Frequently Asked Questions

Does incremental mode affect type checking accuracy?

No. Incremental mode stores information about the previous build so it can skip files that have not changed. The type checking results are identical to a full build. It only affects speed.

Will skipLibCheck hide real errors in my dependencies?

It skips checking .d.ts files, which are type declaration files shipped by libraries. Your own .ts files are still fully checked. Errors in library type declarations are rare and usually not actionable, so skipping them is safe.

When should I set up project references?

When your project is large enough that a full tsc run takes more than a few seconds, or when you have clearly separated packages that change at different rates. For small projects under 100 files, incremental mode alone is usually enough.

Conclusion

A slow TypeScript feedback loop makes you less productive than it should. Enable incremental builds for faster recompilation, add skipLibCheck to avoid checking library types you cannot fix, use project references for large codebases, and pick tsx for dev workflows where type checking is a separate step. Each change is small but together they cut compile times dramatically.