Right, so your builds are slow. You’ve felt that creeping dread as you hit tsc and go make a coffee, only to return and find it’s still chugging away. Welcome to the club. The first rule of TypeScript Performance Club is: you don’t just guess what’s wrong. You use the tools designed to tell you. That’s where --diagnostics and its more verbose cousin, --extendedDiagnostics come in. Think of them as the MRI machine for your ailing compilation process.

What –diagnostics Actually Tells You

Running tsc with the --diagnostics flag is the equivalent of asking your doctor for a basic check-up instead of just complaining you “feel tired.” It gives you a high-level summary of where the TypeScript compiler is spending its time and memory. Let’s run it on a moderately sized project and see what we get.

tsc --diagnostics

You’ll get output that looks something like this:

Files:            1234
Lines:         123456
Nodes:         345678
Identifiers:    123456
Symbols:        234567
Types:          987654
Memory used:  1048576K
I/O Read:         0.01s
I/O Write:        0.02s
Parse time:       1.23s
Bind time:        0.45s
Check time:       7.89s
Emit time:        0.98s
Total time:      10.55s

Now, let’s translate this from Compiler-Speak to English. The first few lines are just stats about the size of your codebase. Interesting, but not immediately actionable. The real gold is in the timings:

  • Parse time: How long it took to turn your source text into an Abstract Syntax Tree (AST). This is mostly I/O and syntax parsing. If this is high, you might have a ton of very large files.
  • Bind time: The compiler creates a “symbol” for every named entity (variables, functions, etc.). This is where it links your import to its corresponding export. Usually quick.
  • Check time: This is the big one. This is where TypeScript does all the heavy lifting of type checking. If your total time is 10 seconds and check time is 8 of them, you know the problem isn’t reading files or writing output—it’s the type system itself doing its (admirably thorough) job.
  • Emit time: How long it takes to transform the AST into JavaScript and write it to disk. If this is high, you might be using a complex transformation tool like Babel in the chain, or have a huge number of files to write.
  • Memory used: This one is crucial. If you see a number here that’s alarmingly high (e.g., multiple gigabytes for a small project), it’s a giant red flag that you’re doing something that explodes the type space, like insane keyof usage on massive types or pathological conditional types.

The Deep Dive: –extendedDiagnostics

If --diagnostics is the check-up, --extendedDiagnostics is the full genome sequencing and a meeting with a team of specialists. It adds a treasure trove of information, most notably the time spent in each part of the program. This is how you find the specific files or patterns that are murdering your performance.

tsc --extendedDiagnostics

The output will include everything from --diagnostics plus a massive breakdown. The most important part is the “Program” section:

...
[Time]
Program:
  ...
  Pre-parse: 0.12s
  Parse: 1.23s
  ResolveModule: 0.45s
  CreateSourceFile: 1.10s # Wait, what?
  CreateProgram: 0.05s
  Bind: 0.45s
  Check: 7.89s
  ...
  I/O:
    ReadFile: 1234 (0.34s)
    WriteFile: 567 (0.98s)
    ...
...

See that CreateSourceFile time? If that’s suspiciously high relative to your parse time, it can be a hint that you’re using a lot of triple-slash directives (/// <reference types="..." />) or old-school module declarations, which force the compiler to create multiple “source” contexts. It’s a weird edge case, but I’ve seen it happen.

The real power here is in the “I/O” section. It tells you exactly how many files it read and wrote. If the number of ReadFile is in the thousands for a project with only a few hundred actual .ts files, congratulations, you’ve found the problem! The compiler is crawling through node_modules reading every .d.ts file it can find. This is almost always the reason for pathological build times, and it’s why you need to…

Correlate the Data with Your Config

The diagnostics don’t exist in a vacuum. They are a direct reflection of your tsconfig.json choices. A high Check time is expected if you have strict: true (and you should, you maniac). But a high Check time coupled with a massive Files count means you’re probably not using the "exclude" property properly and are type-checking node_modules, or you’ve set "maxNodeModuleJs" to a high value for some reason.

A huge number of ReadFile operations almost certainly points to the same issue: the compiler is being told to look at things it shouldn’t. Your tsconfig.json is your fence. It tells the compiler, “These pastures are yours to check; everything else is off-limits.” If your fence is full of holes (exclude is missing or wrong), the compiler will happily wander into the sprawling, chaotic jungle of node_modules and try to type-check every last library you’ve installed. Don’t make it do that. It hates it, and it will punish you with slow builds.

// tsconfig.json
{
  "compilerOptions": {
    // ... your options
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"] // FENCE YOUR COMPILER IN.
}

Run --extendedDiagnostics before and after fixing your include/exclude. The difference in Files count and ReadFile operations will be staggering. It’s the single biggest win for most projects. Remember, the compiler is brilliant but literal. It will do exactly what you tell it to, even if it’s a terrible idea. These flags are how you figure out what terrible idea you accidentally told it to do.