How to debug memory leaks in Node.js applications
memory leaks, heap snapshots, garbage collection, event listener leaks, closure retention, V8 memory tools
What a Memory Leak Looks Like
A memory leak in Node.js manifests as heap memory that grows continuously over time and never decreases. The process eventually crashes with an out-of-memory error or slows to a crawl. The cause is always the same: objects that are no longer needed but remain reachable in the object graph, preventing garbage collection.
Taking and Comparing Heap Snapshots
// Take heap snapshots programmatically
const v8 = require('v8');
// Snapshot before the suspected leak
v8.writeHeapSnapshot('./heap-before.heapsnapshot');
// Run the operation you suspect is leaking
for (let i = 0; i < 1000; i++) await processRequest();
// Snapshot after
v8.writeHeapSnapshot('./heap-after.heapsnapshot');
// Open both in Chrome DevTools under Memory tab, then Load snapshot
// Compare: objects that grew in count are your leak candidates
Common Leak Patterns
The most common Node.js leaks: event listeners added but never removed (call emitter.removeListener when done); closures holding references to large objects after the outer function returns; caches with no eviction policy (use a Map with a size limit or an LRU cache). For event listener leaks, process.on('uncaughtException', handler) inside a request handler is a classic example -- it re-registers on every request and the listeners pile up.
