TypeScript Performance Best Practices
Comprehensive performance optimization guide for TypeScript codebases. Contains 43 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
When to Apply
Reference these guidelines when:
- Writing or refactoring TypeScript code
- Tuning build times, type-checking performance, declaration emit, or editor responsiveness
- Investigating performance regressions
- Reviewing code for performance issues
- Configuring tsconfig.json for new projects or monorepos
Quick Diagnostic Workflow
When TypeScript feels slow, follow this diagnostic sequence:
# Step 1: Get baseline metrics
tsc --extendedDiagnostics --noEmit
# Key metrics to check:
# - Files: Should match expected source count
# - Check time: Usually the bottleneck (>10s is high)
# - Instantiations: High count indicates complex generics
# Step 2: If file count is high
tsc --listFiles | wc -l
tsc --explainFiles 2>&1 | grep -v "@types" | head -50
# Step 3: If Check time is high
tsc --generateTrace ./trace --noEmit
# Open chrome://tracing and load trace/trace.json
# Step 4: If module resolution is slow
tsc --traceResolution 2>&1 | head -200
Rule Categories by Priority
| Priority |
Category |
Impact |
Quick Win |
| 1 |
Build Graph & Incremental |
CRITICAL |
Enable incremental: true |
| 2 |
Program Scope & Inputs |
HIGH |
Narrow include patterns |
| 3 |
Module Resolution & Imports |
HIGH |
Use moduleResolution: "bundler" |
| 4 |
Type Acquisition & Lib Checks |
MEDIUM-HIGH |
Enable skipLibCheck: true |
| 5 |
Type System Complexity |
CRITICAL |
Annotate function return types |
| 6 |
Declaration Emit & Public API |
MEDIUM-HIGH |
Enable isolatedDeclarations |
| 7 |
Editor/tsserver Performance |
MEDIUM-HIGH |
Use project references |
| 8 |
Diagnostics & Profiling |
DIAGNOSTIC |
Run tsc --extendedDiagnostics |
Common Scenarios
Scenario: Slow CI Builds (>60s)
Symptoms: CI takes minutes, incremental doesn't help
Diagnostic:
tsc --extendedDiagnostics --noEmit
# Check: Files count, Check time, Memory used
Likely fixes:
- Enable
incremental: true with cached .tsbuildinfo
- Enable
skipLibCheck: true (10-50% improvement)
- Split into project references (60-70% memory reduction)
- Narrow
include patterns
Scenario: Slow Editor/IntelliSense
Symptoms: Autocomplete lags, hover takes seconds, high memory
Diagnostic:
# Check tsserver memory
ps aux | grep tsserver | awk '{print $6/1024 " MB"}'
Likely fixes:
- Enable
disableReferencedProjectLoad: true
- Enable
disableSolutionSearching: true
- Enable
disableSourceOfProjectReferenceRedirect: true
- Split into project references
Scenario: Type Checking Takes >10s
Symptoms: Check time dominates in diagnostics
Diagnostic:
tsc --generateTrace ./trace --noEmit
# Open chrome://tracing, find slow checkSourceFile operations
Likely fixes:
- Add explicit return type annotations to exported functions
- Simplify large unions (50+ members)
- Replace deep intersections with interface extension
- Name complex types with type aliases
Scenario: Unexpected Files in Build
Symptoms: More files than expected, node_modules source compiled
Diagnostic:
tsc --explainFiles 2>&1 | grep -v "@types" | grep "node_modules"
Likely fixes:
- Add
"exclude": ["node_modules", "dist"]
- Narrow
include to ["src"]
- Remove broad
**/* patterns
Quick Reference
1. Build Graph & Incremental (CRITICAL)
Impact: 50-80% faster rebuilds; essential for monorepos
| Rule |
Impact |
Description |
build-project-references |
CRITICAL |
Split large repos into referenced projects |
build-composite |
CRITICAL |
Enable composite for project reference builds |
build-incremental |
CRITICAL |
Reuse prior type-checking work |
build-tsbuildinfo |
HIGH |
Cache incremental state in stable location |
build-tsc-build |
HIGH |
Use tsc --build for reference graphs |
build-assume-direct-deps |
MEDIUM |
Speed up watch in very large repos |
2. Program Scope & Inputs (HIGH)
Impact: Reduces files parsed; every extra file costs time
| Rule |
Impact |
Description |
scope-tight-include |
HIGH |
Restrict include to source directories |
scope-exclude-build-output |
HIGH |
Exclude dist/build/node_modules |
scope-avoid-broad-globs |
MEDIUM-HIGH |
Avoid **/* in large repos |
scope-use-files-array |
MEDIUM |
Explicit file lists for small packages |
scope-separate-tests |
MEDIUM |
Keep tests in separate tsconfig |
3. Module Resolution & Imports (HIGH)
Impact: Reduces resolution overhead; wrong mode = excessive lookups
| Rule |
Impact |
Description |
resolve-moduleResolution-modern |
HIGH |
Use node16/nodenext/bundler |
resolve-bundler-mode |
HIGH |
Use bundler mode for bundled apps |
resolve-keep-paths-tight |
MEDIUM-HIGH |
Avoid catch-all paths patterns |
resolve-use-packagejson-exports |
MEDIUM |
Use package.json exports/imports |
resolve-baseurl-minimize |
MEDIUM |
Use baseUrl only when needed |
4. Type Acquisition & Lib Checks (MEDIUM-HIGH)
Impact: 10-50% faster type-checking; skips redundant work
| Rule |
Impact |
Description |
types-limit-types |
MEDIUM-HIGH |
Include only needed @types |
types-restrict-typeRoots |
MEDIUM |
Limit global type search paths |
types-skip-lib-check |
HIGH |
Skip .d.ts validation (major win) |
types-typeacquisition-control |
MEDIUM |
Control automatic type acquisition |
types-disable-filename-based-acquisition |
LOW |
Prevent filename-based type downloads |
5. Type System Complexity (CRITICAL)
Impact: Complex types cause O(n^2) checking; simplification = big wins
| Rule |
Impact |
Description |
type-annotate-exports |
CRITICAL |
Named exported types reduce emit cost |
type-annotate-returns |
CRITICAL |
Return annotations reduce inference |
type-name-complex-types |
HIGH |
Named types reduce expansions |
type-avoid-deep-intersections |
HIGH |
Interfaces cached; intersections recomputed |
type-avoid-large-unions |
HIGH |
Large unions cause O(n\*m) checking |
type-export-named-types |
MEDIUM-HIGH |
Named exports reduce .d.ts size |
6. Declaration Emit & Public API (MEDIUM-HIGH)
Impact: Up to 3x faster with isolatedDeclarations (requires --noCheck or alternative emitters like swc/oxc to benefit); parallel emit
| Rule |
Impact |
Description |
dts-emit-declaration-only |
MEDIUM-HIGH |
Skip JS emit when bundler handles it |
dts-declarationDir |
MEDIUM |
Organize .d.ts output separately |
dts-strip-internal |
MEDIUM |
Remove @internal from .d.ts |
dts-isolated-declarations |
CRITICAL |
Enable parallel .d.ts emit |
dts-noCheck-fast-emit |
HIGH |
Skip type-check for .d.ts emit |
7. Editor/tsserver Performance (MEDIUM-HIGH)
Impact: ~3GB to <1GB memory; faster startup, navigation
| Rule |
Impact |
Description |
tsserver-disable-referenced-project-load |
MEDIUM-HIGH |
Load projects on demand |
tsserver-disable-solution-searching |
MEDIUM-HIGH |
Stop upward config search |
tsserver-disable-source-redirect |
MEDIUM-HIGH |
Use .d.ts across boundaries |
tsserver-use-project-references |
HIGH |
Split projects for memory savings |
tsserver-solution-config |
MEDIUM-HIGH |
Root tsconfig with files:[] |
8. Diagnostics & Profiling (DIAGNOSTIC)
Purpose: Tools for finding performance issues, not optimizations themselves
| Rule |
Purpose |
When to Use |
diag-extended-diagnostics |
Detailed timing breakdown |
First diagnostic to run |
diag-diagnostics |
Quick timing overview |
CI summaries |
diag-explain-files |
Why files are included |
Unexpected file count |
diag-trace-resolution |
Module resolution steps |
"Cannot find module" errors |
diag-generate-trace |
Chrome DevTools trace |
Deep type-checking analysis |
diag-list-files |
All files in compilation |
Scope audits |
Recommended Base Configuration
For most TypeScript projects:
{
"compilerOptions": {
// Performance essentials
"incremental": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
// For libraries
"declaration": true,
"declarationMap": true,
"isolatedDeclarations": true,
// Standard strictness
"strict": true,
"noUncheckedIndexedAccess": true,
// Output
"target": "ES2022",
"module": "ESNext"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
For monorepos, add to each package:
{
"compilerOptions": {
"composite": true,
"disableReferencedProjectLoad": true,
"disableSolutionSearching": true,
"disableSourceOfProjectReferenceRedirect": true
}
}
TypeScript 5.6+ Performance Features
Recent TypeScript versions (5.6-5.8) introduced significant performance improvements:
--noCheck Flag (TS 5.6+)
Skip type-checking when you only need transpilation or declaration emit:
# Fast declaration emit without type-checking
tsc --declaration --emitDeclarationOnly --noCheck
# Combine with isolatedDeclarations for maximum speed
tsc --declaration --emitDeclarationOnly --noCheck --isolatedDeclarations
When to use: CI pipelines where type-checking runs separately, or when using swc/esbuild for transpilation.
--libReplacement Flag (TS 5.8+)
Control whether lib.d.ts can be replaced by node_modules types:
{
"compilerOptions": {
"libReplacement": false
}
}
Impact: Prevents unexpected type resolution slowdowns from packages that ship their own lib replacements.
Version Compatibility Table
| Feature |
Minimum TS Version |
Impact |
isolatedDeclarations |
5.5 |
Up to 3x |
--noCheck |
5.6 |
2-10x emit |
--libReplacement |
5.8 |
Varies |
| Swiss Table internals |
5.8 |
5-20% faster |
TypeScript 7 Preview (Experimental)
TypeScript 7, currently in development, is being rewritten in Go for significantly faster compilation. Early benchmarks suggest:
- 10x faster type-checking in large codebases
- 5x faster editor responsiveness
- Full backward compatibility with TS 5.x configs
Status (as of January 2026): Preview builds available. Not production-ready. Monitor TypeScript 7 roadmap for updates.
How to Use
Read individual rule files for detailed explanations and code examples:
rules/build-project-references.md
rules/type-annotate-returns.md
rules/diag-generate-trace.md
Each rule file contains:
- Impact rating with quantified description
- Why it matters with numbered benefits
- Incorrect/Correct code examples
- When NOT to apply (important for avoiding over-optimization)
- Common mistakes to avoid
- Diagnostic commands for verification
- References to official documentation
References