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.