Find What Keeps Your Node Process Memory Alive
The garbage collector can only release objects your program no longer reaches. If a global array, event listener, timer, cache, or closure still points at an object, the collector is doing exactly what you asked: keeping it alive.
A useful memory-leak investigation proves retention under a repeatable workload. "Memory went up" is not enough because healthy runtimes allocate in bursts and collect later.
Build one repeatable workload
Pick the smallest action that makes the graph climb: one request, websocket connect/disconnect, file import, or page render. Run it in batches, then wait for the process to settle. Capture heap snapshots at the same points:
baseline
after 100 runs + settle
after 500 runs + settle
after 1,000 runs + settle
Compare retained object counts, not only total heap size. Look for a class or array whose count grows with each batch and never returns.
Follow the retaining path
The important question is: what still points at this object?
Common roots:
- a process-wide
Mapwith no size limit or eviction; - an event emitter that receives a new listener per request;
- an interval or timeout that captures request state;
- a websocket registry that never removes closed clients;
- closures stored in retry or callback arrays;
- a metrics label containing unbounded user IDs or URLs.
Fix the owner. Removing random references or forcing garbage collection can change the graph for one test without repairing the lifecycle.
Add bounds and cleanup
Every process-wide collection needs an answer for maximum size, expiry, and eviction. Every
listener and timer needs an owner that removes it. Prefer APIs such as once when the event really
is one-shot, and put cleanup in finally when a failure path can otherwise skip it.
Increasing the container memory limit is not a leak fix. It changes the time until the same crash.
Prove the repair
Run the exact workload again. The post-settle heap should form a stable band instead of a staircase. Check that listener counts stop growing and cache size stays inside its stated limit. Then leave a longer soak test running to catch slower retention.
Claude can help compare snapshots and inventory lifecycle code. Keep it pointed at retaining paths and measured counts. A confident list of "likely leaks" without a reproducer is only a starting hypothesis.
Run the starter locally
npm test
npm run validate
npm run sample
The workflow validates fictional snapshot and workload evidence. It returns a draft report and does not attach to or restart a production process.