TypeScript and Node.js profiling, benchmarking, and optimization patterns
Use this skill when profiling, benchmarking, or optimizing TypeScript or Node.js applications. Pair with perf-core for universal methodology.
# CPU profiling (V8 tick processor)
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# Heap snapshot on demand
node --inspect app.js
# Then connect Chrome DevTools and take snapshot
# Heap profiling to file
node --heap-prof app.js
# Trace garbage collection
node --trace-gc app.js
--inspect or --inspect-brkchrome://inspect in Chrome and click the target| Tool | Purpose | Command |
|---|---|---|
clinic doctor | Overall health (event loop, CPU, memory) | clinic doctor -- node app.js |
clinic bubbleprof | Async bottleneck visualization | clinic bubbleprof -- node app.js |
clinic flame | CPU flame graph | clinic flame -- node app.js |
Install: npm install -g clinic
import { bench, describe } from 'vitest'
describe('string concatenation', () => {
bench('plus operator', () => {
let s = ''
for (let i = 0; i < 1000; i++) s += 'x'
})
bench('array join', () => {
const parts: string[] = []
for (let i = 0; i < 1000; i++) parts.push('x')
parts.join('')
})
})
Run with: vitest bench
import { Bench } from 'tinybench'
const bench = new Bench({ time: 1000 })
bench
.add('Map lookup', () => { map.get('key') })
.add('Object lookup', () => { obj['key'] })
await bench.run()
console.table(bench.table())
node --trace-deopt app.jstry/catch in hot loops (move the try/catch outside the loop)arguments object leaking (use rest parameters instead)eval and with (prevent optimization entirely)delete on objects (breaks hidden class, use undefined assignment)| Pattern | Cause | Fix |
|---|---|---|
| Closure references | Inner function holds outer scope alive | Nullify references when done |
| Event listeners | Listeners accumulate without removal | Use once or remove in cleanup |
| Global caches | Unbounded maps/objects grow forever | Use LRU cache with max size |
| Timers | setInterval without clearInterval | Always store and clear timer refs |
| Detached DOM nodes | Removed from DOM but referenced in JS | Nullify references after removal |
# Analyze bundle composition
npx esbuild-visualizer --metadata meta.json --open
# Source map analysis
npx source-map-explorer dist/bundle.js
# Check package size impact before install
npx package-phobia <package-name>
import *import() for code splitting on routes or heavy featuresnpx depcheck for unused packagesimport { monitorEventLoopDelay } from 'node:perf_hooks'
const h = monitorEventLoopDelay({ resolution: 20 })
h.enable()
setInterval(() => {
console.log(`Event loop p99: ${(h.percentile(99) / 1e6).toFixed(1)}ms`)
h.reset()
}, 5000)
setImmediatevitest bench in CI and compare against baselinehyperfine --export-json for CLI tool benchmarks| Anti-Pattern | Better Approach |
|---|---|
| Sync file I/O in hot paths | Use fs/promises or streams |
JSON.parse(JSON.stringify(obj)) for deep clone | Use structuredClone |
| String concatenation in tight loops | Use array + join or template literals |
| Not awaiting promises | Causes memory leaks from unresolved chains |
| CPU-bound work on main thread | Use worker_threads for heavy computation |
Unbounded Promise.all | Use p-limit or batch processing |