How do you find and fix performance bottlenecks in a Node app?

3 minadvancednodejsperformanceprofilingevent-loopmemory-leak

Quick Answer

Measure first: profile CPU with --prof/--inspect or clinic.js, watch event-loop lag, and track memory for leaks with heap snapshots. Common fixes are removing event-loop blocking (offload CPU work), eliminating N+1 queries, adding caching, streaming large data, and tuning concurrency.

Detailed Answer

Answer: Optimize based on measurement, not guesses.

1. Profile CPU usage:

  • node --prof app.js then node --prof-process for a text report.
  • node --inspect + Chrome DevTools, or clinic.js (clinic flame, clinic doctor) for flame graphs.
  • Look for hot functions and synchronous work on the request path.

2. Monitor event-loop lag: High lag means something is blocking the loop.

const { monitorEventLoopDelay } = require('perf_hooks');
const h = monitorEventLoopDelay(); h.enable();
setInterval(() => console.log('loop p99 (ms):', h.percentile(99) / 1e6), 5000);

3. Track memory / find leaks:

  • Watch process.memoryUsage() (RSS/heapUsed) over time; steady growth suggests a leak.
  • Take heap snapshots in DevTools and compare to find retained objects (common causes: unbounded caches/Maps, un-removed event listeners, closures holding large data, growing global arrays).

Common bottlenecks and fixes:

SymptomLikely causeFix
High event-loop lagCPU work on main threadoffload to worker_threads/queue; chunk work
Slow endpoints, high DB loadN+1 queries, missing indexesbatch/join queries, add indexes, cache
Memory grows unboundedleak (caches/listeners/closures)bound caches (LRU+TTL), remove listeners
High memory on big payloadsbuffering large datause streams
Latency spikes under loadthread-pool contention, no poolingraise UV_THREADPOOL_SIZE, use connection pools, limit concurrency

Also: enable gzip/compression, use HTTP keep-alive and DB connection pools, and add caching/CDN. Always re-measure after each change to confirm the win.