Skip to Content
Speed Up tsc With incremental and Smarter Includes
Advanced
0 likes
Tutorials

Speed Up tsc With incremental and Smarter Includes

Count the files tsc reads, narrow the include list and turn on incremental with a cache file. After this, you know which change made the build faster because you measured it.

Skip ahead: If you already know how to read --extendedDiagnostics, skip to "Step 3: say what to compile", then continue.

tsc takes longer than it should on your repository, and you want it faster without guessing. There are two changes that cost almost nothing: tell the compiler exactly which files belong to the project, and turn on incremental. This recipe measures each one. It takes about ten minutes and needs no network beyond the install. Times differ on every machine, so compare the numbers in your own runs and trust the file counts, which do not change.

Step 1: a project that compiles too much

A tsconfig with no include means "everything in this folder". A project like that often picks up scripts, generated files and fixtures nobody meant to type check. This one has an application folder, a scripts folder and a folder of generated fixtures.

{
  "name": "slowdemo",
  "private": true,
  "type": "module",
  "devDependencies": { "typescript": "7.0.2" }
}
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "strict": true,
    "noEmit": true,
    "types": []
  }
}
export function total(prices: number[]): number {
  return prices.reduce((sum, price) => sum + price, 0);
}
export const names = ["ana", "ben", "carla"];

A script generates 300 fixture files with 100 rows each, the way a snapshot tool or a code generator would.

import { mkdirSync, writeFileSync } from "node:fs";

mkdirSync("fixtures", { recursive: true });
for (let i = 0; i < 300; i += 1) {
  const rows = Array.from(
    { length: 100 },
    (_, n) => `  { id: ${n}, label: "row ${i}-${n}", tags: ["a", "b"], active: ${n % 2 === 0}, meta: { source: "fixture", index: ${n} } },`,
  );
  writeFileSync(`fixtures/data_${i}.ts`, `export const rows = [\n${rows.join("\n")}\n];\n`);
}
npm install --no-audit --no-fund --loglevel=error
node make_fixtures.mjs

Step 2: measure

--listFilesOnly prints every file in the program. Count the ones that are not inside node_modules, which are yours.

npx tsc --listFilesOnly | grep -vc node_modules
302

You wrote one application file and the compiler is reading 302 of your files. --extendedDiagnostics shows what that costs. Files and Types are counts and will be the same on your machine. Check time and Total time are the numbers that change from machine to machine.

npx tsc --extendedDiagnostics | grep -E "^(Files|Types):"
Files:              365
Types:           154088
npx tsc --extendedDiagnostics | grep -E "^(Check time|Total time):"
Check time:      0.316s
Total time:      0.433s

Step 3: say what to compile

Add include for the folders that belong to this project. The fixtures and the scripts are not part of the application.

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "strict": true,
    "noEmit": true,
    "types": []
  },
  "include": ["src"]
}
npx tsc --listFilesOnly | grep -vc node_modules
npx tsc --extendedDiagnostics | grep -E "^(Files|Types):"
1
Files:              64
Types:           31866
npx tsc --extendedDiagnostics | grep -E "^(Check time|Total time):"
Check time:     0.191s
Total time:     0.230s

The count dropped from 302 to 1, and the number of types from 154,088 to 31,866. On the machine used for this page the total time went from about 0.4 seconds to about 0.2. The remaining types come from TypeScript's own library files, which every project pays for. If the scripts need type checking, give them their own tsconfig that includes only scripts. Do not leave them in the application's program, where every one of them is checked on every build.

💡 Tip: Look for the accidental includes first. The usual ones are dist or build output (declaration files that get read as source), test fixtures, scripts, and a tsconfig at the repository root that has no include at all.

Step 4: turn on incremental

incremental makes the compiler save what it learned in a .tsbuildinfo file and reuse it next time. With noEmit, you choose where that file goes with tsBuildInfoFile. Put it in a cache folder that is already ignored by git.

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "strict": true,
    "noEmit": true,
    "types": [],
    "incremental": true,
    "tsBuildInfoFile": ".cache/tsc.tsbuildinfo"
  },
  "include": ["src"]
}

Run it twice. The first run has nothing to reuse, and the second finds the file.

npx tsc --extendedDiagnostics | grep -E "^(Total time):"
npx tsc --extendedDiagnostics | grep -E "^(Total time):"
ls .cache
Total time:           0.237s
Total time:           0.043s
tsc.tsbuildinfo

On the machine used for this page the second run took about a fifth of the first (0.24 seconds against 0.04). The saving grows with the size of the project, because only the files that changed, and the files that depend on them, are checked again.

Step 5: keep the cache between CI runs

A cache folder you throw away on every run gives no benefit. In CI, save and restore .cache between runs with your provider's cache feature, keyed on the lockfile. Check that it helps with the same command as above. If the second run is not faster, the file is not being restored or is being written somewhere else.

Common mistakes

  • Adding skipLibCheck first. It can help when libraries are large, but it hides errors in declaration files, so measure the file count before you reach for it.
  • Setting incremental and leaving the .tsbuildinfo file in git. It is a build artefact. Add .cache to .gitignore.
  • Excluding a folder with exclude while another file still imports from it. The compiler reads it anyway, because an import pulls the file in.
  • Judging a change from one run. Time varies from run to run, so repeat each measurement and compare files and types too.

Related course: TypeScript Advanced.

About

Count the files tsc reads, narrow the include list and turn on incremental with a cache file. After this, you know which change made the build faster because you measured it.

Responsible
Jay P
Last Update
10/18/2026
Reading Time
5 min
Was this helpful?
​ 0 ​ 0

Comments (0)

Sign in to comment

No comments yet.