Debugging Node.js Memory Leaks Step-by-Step
Thu Aug 14 2025
Memory climbing? Don't restart blindly—profile intentionally.
1. Capture a Heap Baseline
Run the service with:
NODE_OPTIONS="--inspect" node server.js
Open chrome://inspect
, take a heap snapshot.
2. Reproduce Growth
Trigger realistic traffic:
npx autocannon -c 50 -d 30 http://localhost:3000/api/test
Take a second snapshot when RSS has grown notably.
3. Compare Snapshots
In DevTools, use the Comparison view. Focus on:
- Detached DOM trees (if SSR + JSDOM tests)
- Growing arrays / Maps
- Retained closures capturing large objects
4. Look for Patterns
Common offenders:
- Caches without size limits
- EventEmitter listeners never removed
- Global arrays collecting requests
- setInterval without clear
5. Add Heap Dumps in Prod (Temporary)
import { writeHeapSnapshot } from "v8" process.on("SIGUSR2", () => { writeHeapSnapshot(`heap-${Date.now()}.heapsnapshot`) })
Use only briefly; snapshots can be large.
6. Track Allocation Hotspots
Run with:
node --inspect --trace-gc --trace-gc-verbose server.js
Or use clinic heapprofiler
from Clinic.js.
7. Verify the Fix
After patching suspected code, repeat the load test. Compare RSS / heap stabilized plateau vs continuous slope.
8. Prevent Recurrence
- Add max size + eviction to caches (LRU)
- Monitor heap usage (percent of limit) in metrics
- Run periodic load tests in CI for critical services
9. Triage Checklist
Symptom timeline: Growth trigger (endpoint / job): Retained objects diff summary: Root cause: Fix implemented: Validation evidence: Preventative guardrails:
Leak resolved = predictable plateau under sustained load.