How to Fix a Node.js Memory Leak
Most Node.js memory leaks follow the same pattern: a global Array, Map, or Set that gets appended to on every request but never pruned. The process runs fine at first, then the heap climbs until the OOM killer fires at 3am. Here's how to find it and cap it.
Why the heap keeps climbing
Node.js processes are long-lived. Any collection you allocate at the module level and never clear accumulates entries for the entire lifetime of the process. Under production traffic - thousands of requests per hour - an unchecked global array goes from harmless to hundreds of MB in hours.
The classic shape looks like this:
const requests = []; // global - lives forever
server.on('request', (req) => {
requests.push({ url: req.url, ts: Date.now() });
// nothing ever removes entries
});
After 24 hours of normal traffic, requests holds millions of entries. The heap
grows linearly with uptime - the textbook sign of an unbounded collection.
Find the leak
Before touching code, confirm WHERE memory is growing. Two fast approaches:
/statsendpoint - if the app exposes a count, watch it climb:curl localhost:3000/statsprocess.memoryUsage()- logheapUsedon a timer to see steady growth with no plateau
A heap that never stabilizes between requests points to a module-level collection.
Scan your top-level code for any const arr = [] or const map = new Map() that
is appended to in a request handler.
Cap the collection with .shift()
The simplest fix is a length cap. After each push, drop entries from the front until the array is back inside the limit:
const requests = [];
const MAX_REQUESTS = 100;
server.on('request', (req) => {
requests.push({ url: req.url, ts: Date.now() });
while (requests.length > MAX_REQUESTS) requests.shift();
// array never exceeds 100 entries - O(1) memory
});
Memory growth is now O(1) regardless of traffic volume.
Alternative: time-based pruning
If you need "last N seconds" rather than "last N entries", prune by timestamp:
const WINDOW_MS = 60_000; // keep the last 60 seconds
server.on('request', (req) => {
const now = Date.now();
requests.push({ url: req.url, ts: now });
// drop entries older than the window
while (requests.length && requests[0].ts < now - WINDOW_MS) {
requests.shift();
}
});
Verify the fix
After applying either fix, run a load test and watch /stats:
bash /workspace/load-test.sh # send 1000 requests
curl -s http://localhost:3000/stats # should plateau at <= 100
The count should hit the cap and stay flat, not keep climbing.
In production: go deeper
clinic.js/heapdump- generate a V8 heap snapshot to confirm the allocation site when the leak isn't obvious from a quick scan.lru-cacheornode-cache- a proper LRU or TTL cache handles eviction automatically and is safer for shared caches than a hand-rolled shift loop.- Heap metrics - expose
process.memoryUsage().heapUsedto your monitoring system; alert before the process hits its memory limit.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Spotting an unbounded global array that grows on every request
- Capping a collection with a length limit using .shift()
- Verifying memory stabilizes under load with a /stats endpoint
FAQ
What causes memory leaks in Node.js?
The most common cause is a global collection (Array, Map, Set, or EventEmitter listener list) that is appended to on every request but never pruned. Because Node processes are long-lived, the collection grows indefinitely until the process is killed by the OOM handler.
How do I fix an unbounded array in Node.js?
Add a length cap after each push - use while (arr.length > MAX) arr.shift() to drop the oldest entry. This keeps memory O(1) regardless of traffic. For time-based expiry, prune entries whose timestamp is older than your window.
How do I find a Node.js memory leak?
Log process.memoryUsage().heapUsed on a timer and look for steady linear growth with no plateau. Then scan module-level code for global arrays or Maps that are written to in request handlers. Tools like clinic.js and heapdump generate V8 heap snapshots to pinpoint the allocation site.
What causes a memory leak in Node.js?
Usually a reference that is never released - a global array or map that grows forever, event listeners added but never removed, or closures holding large objects. The heap climbs until the process is OOM-killed; cap or clear the collection to fix it.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →