TypeScript Diagnostics: Reading the Numbers
The four tsc flags that tell you what TypeScript is doing — and what to do with the numbers they surface.
Before you can fix TypeScript performance, you have to know what's actually slow. The good news: TypeScript ships its own diagnostic tools. The bad news: most people don't know they exist until they're deep in a multi-hour debugging session.
There are four flags I reach for, in roughly this order.
--extendedDiagnostics
This is the first stop. It adds a timing and memory breakdown to your normal type check output:
tsc --extendedDiagnosticsYou'll see output like:
Files: 1,247
Lines of Library: 37,842
Lines of Definitions: 162,891
Lines of TypeScript: 89,441
Lines of JavaScript: 0
Lines of JSON: 2,103
Lines of Other: 0
Identifiers: 423,182
Symbols: 318,491
Types: 204,762
Instantiations: 1,847,293
Memory used: 412,284K
Assignability cache size: 184,221
Identity cache size: 4,832
Subtype cache size: 2,104
I/O Read time: 0.18s
Parse time: 1.24s
ResolveModule time: 0.31s
ResolveLibrary time: 0.09s
Program emit time: 0.04s
Total time: 3.18sThe numbers that matter most:
- Files: If this is dramatically higher than you expect, something is pulling in files you didn't intend. This is where barrel file problems show up.
- Instantiations: The number of times TypeScript had to instantiate generic types. High instantiation counts are the fingerprint of complex conditional types.
- Memory used: If this is over 512MB, you're in trouble on standard machines. Over 1GB and you'll start seeing the TypeScript server crash.
Running this on CI before and after a change gives you a baseline to catch regressions early.
--listFiles
When the Files count in --extendedDiagnostics is higher than expected, this flag tells you exactly which files TypeScript is checking:
tsc --listFilesThe output is a flat list of every file included in the program. Pipe it somewhere useful:
tsc --listFiles 2>&1 | grep node_modules | wc -lIf that returns anything above zero with skipLibCheck: false, you're doing unnecessary work. More commonly, --listFiles reveals that your tsconfig.json root is pulling in test files, scripts directories, or entire packages you didn't mean to include.
The fix is almost always tightening the include array in your tsconfig.json:
{
"include": ["src/**/*.ts", "src/**/*.tsx"]
}Not ".". Not "**/*". Specific directories.
--explainFiles
--listFiles tells you what's included. --explainFiles tells you why:
tsc --explainFilesThe output looks like:
../../node_modules/@types/lodash/index.d.ts
Library referenced via 'lodash' from file 'src/utils/format.ts'
Matched by include pattern '**/*' in 'tsconfig.json'This is invaluable for the "I don't know why TypeScript is including this" problem. You'll often find that a single import somewhere in your codebase is pulling in a giant type definition file you didn't expect — and this tells you exactly which import is responsible.
It's also how you diagnose barrel file cascades. When you see a single component file that has a chain of 40 files below it in --explainFiles, that's a barrel file pulling in the entire module graph.
--generateTrace
The most powerful tool, and the most involved. This generates a performance trace file compatible with Chrome DevTools:
tsc --generateTrace ./trace-outputIt creates two files in the specified directory: trace.json and types.json.
To read it:
- Open Chrome and navigate to
chrome://tracing - Load the
trace.jsonfile - Look at the timeline
You'll see a waterfall breakdown of every phase TypeScript went through — which files took longest to parse, which types took longest to instantiate, which modules took longest to resolve.
The types.json file is a separate view: a catalogue of every type TypeScript constructed during the check, with its ID and how it was built. When you have a type that's being instantiated thousands of times, this is how you find it.
It's genuinely the most useful tool for diagnosing deep type complexity issues. The output format is the same one used for profiling JavaScript in Chrome DevTools — so if you've read flame charts before, this is immediately familiar.
The Diagnostic Workflow
Here's how I use these in practice:
- Establish a baseline: Run
--extendedDiagnosticson main, save the numbers - Identify the problem category:
- High file count →
--listFilesthen--explainFiles - High instantiation count →
--generateTrace - High memory → usually barrel files or complex generics; check both
- High file count →
- Make a targeted change (not three changes at once)
- Measure again with
--extendedDiagnostics
The temptation is to guess at the problem and try a bunch of fixes simultaneously. That makes it impossible to know what actually helped. One change, measure, repeat.
The diagnostic tools work the same way whether the slowdown is in local type checking or CI — which is useful, because CI is often where the problem is worst and where it's hardest to debug interactively.
Keep reading
Enjoyed this? Get more like it.
Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.