How to Fix Memory Leaks in JavaScript (With Examples)
Memory leaks in JavaScript are more common than most developers realize. They cause slow performance, increased memory usage, and, eventually, browser crashes or Node.js process failures. Memory management isn't optional if you're working on long-running applications—single-page apps, real-time dashboards, or APIs.
This guide shows you how to find and fix JavaScript memory leaks, with working examples you can apply today.
What Is a Memory Leak in JavaScript?
A memory leak happens when your code keeps a reference to data you no longer need. JavaScript’s garbage collector frees up memory when an object is no longer reachable. But if you accidentally keep references alive, the memory stays allocated, and the garbage collector skips it.
Why Memory Leaks Matter
- Performance degrades over time
- Apps slow down or freeze after prolonged use
- Server processes consume more memory, causing higher cloud costs or crashes
- Hard-to-debug bugs creep in as systems get stressed
How to Spot a Memory Leak in JavaScript
- Consistent memory growth over time
If memory usage keeps climbing and never drops (even after removing elements or navigating away from pages), you probably have a leak. - Frequent garbage collection without memory drops
You might notice the garbage collector is running more often, but your memory usage isn’t decreasing. - Performance profiling in DevTools or Node.js shows retained objects
Detached DOM nodes, closures, and listeners can stick around when they shouldn’t.
Tools to Detect JavaScript Memory Leaks
Chrome DevTools
- Performance Tab → Record and look for memory usage over time
- Memory Tab → Take Heap Snapshots to see what’s holding references
- Allocation Instrumentation → Tracks what’s being allocated and retained
Node.js
--inspect
flag + Chrome DevToolsclinic.js
for automated profilingheapdump
+memwatch-next
to capture and inspect heap snapshots manually
Common Memory Leak Patterns (With Fixes)
1. Accidental Global Variables
Global variables persist for the entire session. If you forget let
, const
, or var
, you’ve created a global without realizing it.
Bad Code
function doSomething() {
leaked = 'oops'; // Implicit global
}
Fix
function doSomething() {
const safe = 'fixed';
}
Pro Tip
Use 'use strict';
at the top of your scripts. It’ll throw errors on accidental globals.
2. Timers and Intervals Not Cleared
setInterval
and setTimeout
hold references in their callbacks. If you don’t clear them, they’ll keep running even after the object they refer to is gone.
Bad Code
function startPolling(
) { const element = document.getElementById('data'
); setInterval(() =>
{ element.innerText = 'Polling...'
; }, 1000
);
}
Even if element
is removed from the DOM, setInterval
keeps it alive.
Fix
function startPolling(
) { const element = document.getElementById('data'
); const interval = setInterval(() =>
{ if (!document.body.contains
(element)) { clearInterval
(interval); return
;
} element.innerText = 'Polling...'
; }, 1000
);
}
Pro Tip
Always clearInterval
and clearTimeout
in cleanup code. If you’re in a framework like React or Vue, use the unmount lifecycle hooks.
3. Closures Retaining Unneeded Data
Closures are a common source of leaks when they capture large objects or DOM nodes you no longer need.
Bad Code
function outer(
) { const hugeArray = new Array(1000000).fill('data'
);
return function inner(
) { console.log(hugeArray[0
]);
};
}const leaky = outer
();
hugeArray
stays in memory as long as inner
is reachable.
Fix
Null out large references if they’re not needed anymore.
function outer(
) { let hugeArray = new Array(1000000).fill('data'
);
const inner = (
) => { console.log(hugeArray[0
]);
}; hugeArray = null; // Release reference
inner;
return
}const fixed = outer
();
4. Detached DOM Nodes Still Referenced
If you remove a DOM node from the page but keep a reference to it, it won’t be garbage collected.
Bad Code
let
elements = [];function addElement(
) { const el = document.createElement('div'
); document.body.appendChild
(el); elements.push
(el);
}function removeElement(
) { const el = elements.pop
(); document.body.removeChild
(el); // Still held by el and elements array
}
Fix
Break all references after removing nodes.
function removeElement(
) { const el = elements.pop
(); document.body.removeChild
(el); // Release reference
;
el = null
}
Better yet, clean up arrays entirely if the references are no longer useful:
elements.length = 0
;
5. Event Listeners Not Removed
Adding event listeners is easy. Forgetting to remove them when the element is gone is how leaks happen.
Bad Code
const button = document.getElementById('myButton'
);function handleClick(
) { console.log('clicked'
);
}button.addEventListener('click'
, handleClick);// button gets removed later without cleanup
Fix
Explicitly remove the listener when cleaning up:
button.removeEventListener('click'
, handleClick);
Or, use { once: true }
when adding the listener:
button.addEventListener('click', handleClick, { once: true
});
6. Unbounded Caches or Data Stores
If you’re caching things without limits, you’ll eventually hit memory problems.
Bad Code
const
cache = {};function getData(key
) { if
(!cache[key]) { cache[key] = fetchData
(key);
} return
cache[key];
}
Every key
you store keeps memory alive indefinitely.
Fix
Use WeakMap
for object keys, or implement an LRU cache with size limits.
WeakMap
example (keys must be objects):
const cache = new WeakMap
();function getData(obj
) { if (!cache.has
(obj)) { cache.set(obj, fetchData
(obj));
} return cache.get
(obj);
}
Prevent Memory Leaks: Code Patterns and Practices
- Use
let
andconst
, nevervar
- Keep scopes small
Minimize long-lived closures and large function scopes - Use
WeakMap
andWeakSet
for object references that shouldn’t prevent garbage collection - Always clear timers and intervals
- Remove event listeners when the target node is removed
- Detach and dereference DOM nodes
Don’t leave references hanging around in arrays, maps, or properties - Profile regularly, not just when things go wrong
Use Chrome DevTools or Node.js profilers as part of your workflow - Limit cache sizes, and clean up old entries
LRU and TTL strategies work well here
Diagnosing and Fixing Memory Leaks in DevTools (Step by Step)
In Chrome DevTools:
- Open Memory tab
- Click Take Snapshot
- Interact with your app (add/remove elements, navigate)
- Take another snapshot
- Compare heap differences
- Look for objects that shouldn’t still be alive
- Look for Detached DOM trees or listeners still attached to removed nodes
In Node.js:
- Start your app with
node --inspect
- Open
chrome://inspect
in Chrome - Look for memory snapshots, retained objects
- Force garbage collection (
global.gc()
, but only works with--expose-gc
flag)
TL;DR
- Memory leaks in JS happen when objects stick around longer than they should
- Watch out for globals, closures, event listeners, and timers
- Clean up your DOM nodes and break references
- Use
WeakMap
andWeakSet
when holding onto objects - Regular profiling and testing is the only way to keep your app clean