Skip to content

Node.js Memory Leaks Detection — Complete Guide to Debugging Heap Growth

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js Memory Leaks Detection. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js memory leaks occur when objects are unintentionally retained by the garbage collector, causing heap growth over time until the process runs out of memory and crashes.

What You'll Learn

By the end of this tutorial, you'll detect memory leaks using heap snapshots, use GC tracing, identify common leak patterns like event listeners and closures, and implement monitoring.

Why Leak Detection Matters

Memory leaks cause production outages, degrade performance through increasing GC pauses, and waste cloud resources. Early detection prevents costly incidents.

Real-World Use

A microservice crashes every 48 hours. Heap snapshot comparison reveals an Express route handler that registers new event listeners on each request without removing them, growing unbounded.

Leak Detection Path

flowchart LR
  A[Profiling] --> B[Memory Leaks]
  B --> C[PM2]
  C --> D[Security]
  D --> E[Docker]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Common Leak: Event Listeners

Registering event listeners without removing them is the most common leak source.

const http = require("node:http");
const server = http.createServer((req, res) => {
  // BAD: New listener on every request, never removed
  process.on("customEvent", () => {
    // This listener accumulates with every request
  });
  // GOOD: Register once, or use once()
  res.end("ok");
});
// Inspect listener count:
setInterval(() => {
  console.log("Listeners:", process.listenerCount("customEvent"));
}, 1000);

Common Leak: Closure Captures

Closures that capture large objects prevent them from being garbage collected.

function createLeak() {
  const largeData = new Array(1000000).fill("leak");
  return function() {
    // This closure holds reference to largeData
    console.log(largeData.length);
  };
}
const leaks = [];
setInterval(() => {
  leaks.push(createLeak());
  console.log(`Leaked ${leaks.length} closures`);
}, 100);

Common Leak: Cache Without Eviction

Unbounded caches grow until memory is exhausted.

const cache = new Map();
async function getUser(id) {
  if (cache.has(id)) return cache.get(id);
  const user = await fetchUserFromDB(id);
  cache.set(id, user);
  return user;
}
// FIX: Add size limit and eviction
class BoundedCache {
  constructor(maxSize = 1000) {
    this.maxSize = maxSize;
    this.cache = new Map();
  }
  get(key) { return this.cache.get(key); }
  set(key, value) {
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

Common Leak: Object References in Arrays

Adding objects to persistent arrays without clearing causes growth.

const requestLog = [];
const http = require("node:http");
const server = http.createServer((req, res) => {
  requestLog.push({
    url: req.url,
    timestamp: Date.now(),
    headers: req.headers, // Retains large objects
  });
  res.end("ok");
});
// FIX: Limit array size or use a circular buffer
class CircularBuffer {
  constructor(size = 1000) {
    this.buffer = new Array(size);
    this.size = size;
    this.index = 0;
  }
  add(item) {
    this.buffer[this.index] = item;
    this.index = (this.index + 1) % this.size;
  }
}

Using --trace-gc for GC Events

Enable GC tracing to see garbage collection frequency and duration.

// Run: node --trace-gc app.js
// Output shows GC events with type, duration, and heap sizes
console.log("Run with: node --trace-gc app.js");
console.log("GC types: Scavenge (young), Mark-sweep (old)");
console.log("High GC frequency indicates allocation pressure");

Common Mistakes

1. Ignoring the Event Emitter Default MaxListeners Warning

The warning at 10 listeners per event often signals a listener leak. Investigate before increasing.

2. Assuming WeakRef Solves All Leaks

WeakRef helps but does not prevent leaks. Objects must still become unreachable through normal references.

3. Not Testing Memory Under Load

Some leaks only appear under production load. Test with sustained traffic for 30+ minutes.

4. Forgetting Global Variables

Data attached to the global object (global, globalThis) is never garbage collected.

5. Confusing RSS with Heap Usage

RSS includes compiled code, stack, and external allocations. Use heap snapshots for JavaScript objects.

Practice Questions

1. What is the most common cause of memory leaks in Node.js?

Event listeners registered without removal. Each registration keeps a reference to the listener function.

2. How do you compare two heap snapshots?

Take a snapshot, perform actions, take another snapshot. Load both in Chrome DevToolsk "DevTools" >}} and compare objects.

3. What does the --trace-gc flag do?

Prints garbage collection events with timestamps, type, and heap statistics to stderr.

4. How does a closure cause a memory leak?

A closure retains references to variables in its outer scope. If the closure is long-lived, those variables cannot be GC'd.

5. Challenge: Create a memory-leaking server and then fix it with proper cleanup.

// Leaking version
app.get("/leak", (req, res) => { process.on("data", () => {}); res.end("ok"); });
// Fixed version
app.get("/fixed", (req, res) => { process.once("data", () => {}); res.end("ok"); });

FAQ

What is the difference between heap and RSS memory?

Heap is allocated JavaScript objects. RSS includes heap + C++ objects, compiled code, and stack.

How do I find the largest objects in a heap snapshot?

In Chrome DevTools Memory tab, use the 'Comparison' view and sort by 'Retained Size'.

What is a detached DOM tree? (in Node.js)

Rare in Node.js. Happens when native bindings retain references across GC cycles.

Can worker threads have memory leaks?

Yes. Each worker has its own heap. Messages between threads can create retained references.

What is heap fragmentation?

Memory divided into small unusable chunks. Common with many small short-lived objects.

Mini Project: Memory Leak Detector

Build a monitoring script that warns about abnormal heap growth.

const v8 = require("node:v8");
const fs = require("node:fs");
class LeakDetector {
  constructor(thresholdMB = 50) {
    this.thresholdBytes = thresholdMB * 1024 * 1024;
    this.lastHeap = null;
  }
  check() {
    const stats = v8.getHeapStatistics();
    const current = stats.used_heap_size;
    if (this.lastHeap !== null) {
      const growth = current - this.lastHeap;
      if (growth > this.thresholdBytes) {
        console.warn(`Potential leak: ${(growth / 1024 / 1024).toFixed(1)}MB growth`);
        const snapshot = v8.getHeapSnapshot();
        snapshot.pipe(fs.createWriteStream(`/tmp/heap-${Date.now()}.heapsnapshot`));
      }
    }
    this.lastHeap = current;
  }
  start(intervalMs = 30000) {
    setInterval(() => this.check(), intervalMs);
  }
}

What's Next

Node.js PM2 Node.js Docker Node.js Security

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro