Node.js Memory Leak Detector
Diagnose and fix Node.js memory leaks using heap snapshots, clinic.js, and process.memoryUsage(). Covers the most common leak patterns and production monitoring strategies.
Quick Memory Health Check
// memory-monitor.ts — add to any Node.js app
function logMemory(label = '') {
const m = process.memoryUsage();
console.log(`[Memory${label ? ' ' + label : ''}]`, {
heapUsed: `${(m.heapUsed / 1024 / 1024).toFixed(1)} MB`,
heapTotal: `${(m.heapTotal / 1024 / 1024).toFixed(1)} MB`,
rss: `${(m.rss / 1024 / 1024).toFixed(1)} MB`,
external: `${(m.external / 1024 / 1024).toFixed(1)} MB`,
});
}
// Log every 60 seconds — if heapUsed grows continuously: leak suspected
setInterval(() => logMemory('60s'), 60_000).unref();
// Or use with load test:
logMemory('before');
await runLoadTest();
global.gc?.(); // force GC (start with --expose-gc flag)
logMemory('after'); Heap Snapshot with Chrome DevTools
# 1. Start app with inspector node --inspect server.js # or attach inspector to running process: node --inspect=0.0.0.0:9229 server.js # 2. Open Chrome → chrome://inspect → click your target # 3. Memory tab → Heap snapshot → Take snapshot (before) # 4. Perform the leaking action 10-20 times # 5. Take snapshot (after) # 6. Switch dropdown to "Comparison" mode # 7. Sort by "# Delta" — growing object counts indicate the leak
// Programmatic heap snapshot (useful in staging)
const v8 = require('v8');
const path = require('path');
function takeHeapSnapshot() {
const filename = `heap-${Date.now()}.heapsnapshot`;
const fullPath = v8.writeHeapSnapshot(path.join('/tmp', filename));
console.log(`Heap snapshot written to: ${fullPath}`);
return fullPath;
}
// Trigger on SIGUSR2 (kill -USR2 <pid>)
process.on('SIGUSR2', () => takeHeapSnapshot()); clinic.js — Automated Diagnosis
# Install clinic.js npm install -g clinic autocannon # 1. Run Doctor (auto-diagnoses memory, CPU, event loop) clinic doctor -- node server.js # In a second terminal, generate load: autocannon http://localhost:3000 -d 30 # 2. Stop the server (Ctrl+C) — clinic generates HTML report automatically # 3. Flame graph for CPU hotspots clinic flame -- node server.js autocannon http://localhost:3000 -d 30 # 4. Bubbleprof for async bottlenecks clinic bubbleprof -- node server.js autocannon http://localhost:3000 -d 30
Common Leak Patterns & Fixes
1. Event Listener Leaks
// BAD: adds listener on every request — never removed
app.get('/stream', (req, res) => {
emitter.on('data', (chunk) => res.write(chunk)); // LEAK
});
// GOOD: remove listener on request close
app.get('/stream', (req, res) => {
const handler = (chunk) => res.write(chunk);
emitter.on('data', handler);
req.on('close', () => emitter.off('data', handler)); // cleanup
});
// GOOD: use once() for one-time events
emitter.once('connected', () => init());
// Monitor listener counts
emitter.setMaxListeners(20); // warns when exceeded (default: 10)
console.log(emitter.listenerCount('data')); 2. Unbounded Caches
// BAD: Map grows indefinitely
const cache = new Map();
app.get('/data/:id', (req, res) => {
const result = expensiveCompute(req.params.id);
cache.set(req.params.id, result); // LEAK: never evicted
res.json(result);
});
// GOOD: LRU cache with max size
import LRU from 'lru-cache';
const cache = new LRU({ max: 1000, ttl: 1000 * 60 * 5 }); // 1000 entries, 5min TTL
app.get('/data/:id', (req, res) => {
if (cache.has(req.params.id)) return res.json(cache.get(req.params.id));
const result = expensiveCompute(req.params.id);
cache.set(req.params.id, result);
res.json(result);
}); 3. Closure Capturing Large Data
// BAD: callback captures entire largeData in closure
async function processReport(id: string) {
const largeData = await fetchLargeDataset(id); // 50MB object
const summary = largeData.items.length;
// This setTimeout keeps largeData alive for 5 minutes!
setTimeout(() => {
console.log(`Report ${id} processed: ${summary} items`);
// largeData is captured in closure even though unused
}, 300_000);
}
// GOOD: capture only what you need
async function processReport(id: string) {
const largeData = await fetchLargeDataset(id);
const summary = largeData.items.length;
// largeData is no longer referenced after this line — GC can collect it
setTimeout(() => console.log(`Report ${id}: ${summary} items`), 300_000);
} Production Memory Monitoring
// Prometheus metrics for memory (with prom-client)
import client from 'prom-client';
const heapGauge = new client.Gauge({
name: 'nodejs_heap_used_bytes',
help: 'Node.js heap used in bytes',
});
setInterval(() => {
heapGauge.set(process.memoryUsage().heapUsed);
}, 15_000).unref();
// Alert in Grafana when:
// - heapUsed grows > 10MB/hour for 3 hours straight
// - heapUsed > 80% of --max-old-space-size limit
# Start with memory ceiling (crash fast rather than degrade)
node --max-old-space-size=512 server.js # 512MB limit Frequently Asked Questions
How do I know if my Node.js app has a memory leak?▼
Signs of a memory leak: heap memory usage grows continuously under steady traffic and never decreases, the process eventually crashes with JavaScript heap out of memory, or response times degrade over time as GC pressure increases. Monitor with process.memoryUsage().heapUsed over time. If it grows by 10MB+ per hour under constant load and doesn't plateau, you have a leak. Tools: Node.js --inspect flag + Chrome DevTools heap snapshots, clinic.js, or process.memoryUsage() logging in production.
What are the most common causes of Node.js memory leaks?▼
The most common causes: (1) Event listeners added but never removed — every addListener without a corresponding removeListener leaks. (2) Unbounded caches — Maps or arrays that grow without eviction (use LRU cache). (3) Closures capturing large objects — inner functions referencing outer scope objects prevent GC. (4) Global variables accumulating data — never append to global arrays/objects in request handlers. (5) setInterval/setTimeout callbacks that reference growing data. (6) Circular references in custom objects (rarely an issue in modern V8, but still possible).
How do I take a heap snapshot in Node.js?▼
Start your app with node --inspect server.js. In Chrome, open chrome://inspect and click your target. Open Memory tab → take a heap snapshot. To find leaks: take snapshot 1, perform the leaking action N times, take snapshot 2. Switch the comparison mode to 'Comparison' and sort by Delta count or retained size. Objects that keep growing indicate the leak source. The Retained Size column shows how much memory would be freed if the object were GC'd.
What is clinic.js and how does it help with Node.js performance?▼
Clinic.js is a suite of diagnostic tools for Node.js: Clinic Doctor detects performance issues (event loop delays, memory leaks, CPU profiling) automatically. Clinic Flame generates an interactive flame graph for CPU profiling. Clinic Bubbleprof visualizes async operations and identifies bottlenecks. Run npx clinic doctor -- node server.js, generate load with a tool like autocannon, then stop and clinic generates an HTML report. It's the fastest way to get a high-level diagnosis without deep manual instrumentation.
How do I detect memory leaks in production Node.js?▼
For production, use process.memoryUsage() to log heap metrics to your monitoring system (Datadog, Prometheus, CloudWatch) every 30-60 seconds. Alert when heapUsed exceeds a threshold or grows by more than X MB/hour. Use --max-old-space-size to set a memory ceiling and expose OOM crashes in metrics. For heap snapshots in production, use the node:v8 module: const v8 = require('v8'); v8.writeHeapSnapshot() creates a snapshot file you can download and analyze offline. Some APM tools (Elastic APM, New Relic) can capture heap snapshots remotely.
What is an event listener leak and how do I prevent it?▼
An event listener leak occurs when addListener is called repeatedly without the corresponding removeListener, causing the callback (and any closure variables it captures) to accumulate in memory. Common patterns: adding event listeners inside request handlers (runs on every request), adding listeners inside component-like functions that get recreated. Fix: use emitter.once() for one-time listeners, always pair addListener with removeListener in cleanup code, use AbortController to remove multiple listeners at once, and monitor with emitter.listenerCount() or set a lower maxListeners limit to trigger warnings early.
Related Products
on GumroadAI Prompt Engineering Toolkit
58 structured prompt templates for Claude, ChatGPT, and Gemini — organized by use case. Code Review, Debugging, Documentation, Architecture, and 10 model-specific tips. Stop writing prompts from scratch.
Buy on Gumroad →AI Developer Prompts Pack
55 battle-tested prompt templates for the workflows developers actually use: code review, debugging, architecture, documentation, and testing. Stop writing prompts from scratch.
Buy on Gumroad →TypeScript Quick Reference Toolkit
5 practical reference files for TypeScript developers: cheatsheet, TS vs JS comparison, 20 utility types with real examples, tsconfig best practices template, and 15 common mistakes guide. Print it. Keep it on your desk.
Buy on Gumroad →You Might Also Need
JavaScript Memory Leak Detector
Detect common JavaScript and React memory leak patterns. Analyzes for unremoved event listeners, uncleaned intervals, unsubscribed observables, and unguarded async updates.
Open free tool →
Web Performance Checklist
Interactive web performance checklist covering images, fonts, JavaScript, CSS, caching, and server. Check off items to track your score out of 100 and export as Markdown.
Open free tool →
JS Prototype Chain Visualizer
Explore JavaScript prototype chains interactively. Visualize built-in types (Array, Map, Promise, Error) and custom classes with property lookup path diagrams.
Open free tool →
More Free Tools
Related Articles
Node.js Memory Management and Profiling: Find and Fix Memory Leaks in 2026
Practical guide to Node.js memory management, heap profiling, and memory leak detection. Covers V8 garbage collection, heap snapshots, clinic.js, and production monitoring techniques.
Astro 5 Complete Guide 2026: Content Layer, Server Islands & Performance
Complete Astro 5 guide for 2026: Content Layer API, Server Islands architecture, View Transitions, Actions, performance optimization, and deployment. Build faster websites with less JavaScript.
Bun 2.0 New Features Deep Dive: Bundler, Hot Reload, Package Manager & Runtime Upgrades
Bun 2.0 complete guide: new bundler improvements, hot reload enhancements, package manager upgrades, Node.js compatibility gains, and performance benchmarks for 2026.
Bun Runtime for Production 2026: Speed Benchmarks, Node.js Migration & Best Practices
Is Bun ready for production in 2026? Comprehensive guide to Bun runtime performance benchmarks vs Node.js, npm/package migration, built-in bundler, test runner, TypeScript support, and real-world production deployment best practices.
Deno 2 vs Node.js 22 vs Bun: JavaScript Runtime Comparison 2026
In-depth comparison of Deno 2, Node.js 22, and Bun in 2026: performance benchmarks, npm compatibility, TypeScript support, use cases, and which runtime to choose for your next project.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →