How to Debug a Memory Leak in Node.js: A Step-by-Step Guide

You’ve seen the signs: your Node.js API starts fast, but after a few hours, response times creep up. Then the container restarts. You’ve got a memory leak.

The good news? Modern tools make tracking it down far easier than you think.

What You’ll Learn

· How to confirm it’s a leak, not normal growth
· Capturing heap snapshots without stopping your app
· Finding the guilty object in Chrome DevTools

Step 1: Prove It’s a Leak

Run your process with:node --inspect app.js

Open chrome://inspect, attach to your process, and take a heap snapshot. Then force a garbage collection (click the trash can icon), wait 30 seconds, and take another snapshot. If memory doesn’t return to baseline, you have a leak.

Step 2: Compare Snapshots

In DevTools, switch to Comparison view. Look for “size delta” – large positive numbers mean objects aren’t being freed. Expand the constructor list and focus on your own classes or large arrays.

Step 3: Trace the Retainer

Click on a suspicious object. The retainers panel shows why it’s still referenced – often a forgotten event listener, a growing array in a closure, or a global variable.

Common real-world culprits:

· Missing .removeListener() on long-lived EventEmitters
· Caching user data without a TTL or size limit
· Accidentally adding variables to global

Step 4: Fix and Verify

Add process.memoryUsage().heapUsed to a /health endpoint, then load test with autocannon or k6. The fix is confirmed when memory stabilizes after multiple GC cycles.

Pro Tips for Production

· Use node –inspect only temporarily – it has overhead
· For always-on monitoring, try clinic or prom-client with Grafana
· Set up a CI test that fails if heap grows >10% after 1000 requests

Summary

Memory leaks aren’t magic. Heap snapshots + comparison view reveal exactly what’s stuck in memory. Next time your Node.js app gets sluggish, you’ll know exactly where to start.

Leave a Comment