Memory Leaks in JavaScript
A memory leak occurs when a program keeps memory it no longer needs. In JavaScript, this happens when objects are no longer used but remain in memory because references to them still exist. Over time, this can cause performance degradation, slow UIs, and even browser crashes.
The JavaScript garbage collector uses reference counting and reachability analysis to determine which variables are still in use. If a reference to an object remains, it won’t be collected—even if it’s not actually needed.
Common Sources of Memory Leaks and How to Avoid Them
1. Over-retention of Object Scope
If a function closes over an object, the entire object remains in memory—even if only one property is needed.
Example:
var foo = {
bar1: memory(), // 5KB
bar2: memory() // 5KB
};
function clickEvent(){
alert(foo.bar1[0]);
}
Though clickEvent()
only uses bar1
, the whole foo
object (10KB) is retained in scope.
Fix:
Extract just what you need:
var bar1 = memory();
function clickEvent(){
alert(bar1[0]);
}
2. Leaking DOM Elements
Keeping references to DOM elements that have been removed from the document can leak memory.
Leaking Example:
var one = document.getElementById("one");
var two = document.getElementById("two");
one.addEventListener('click', function(){
two.remove();
console.log(two); // still accessible, leaked
});
Fix 1 – Query inside the event:
var one = document.getElementById("one");
one.addEventListener('click', function(){
var two = document.getElementById("two");
if (two) two.remove();
});
Fix 2 – Unregister handler after use:
var one = document.getElementById("one");
function handler() {
var two = document.getElementById("two");
if (two) two.remove();
one.removeEventListener("click", handler);
}
one.addEventListener("click", handler);
3. Global Variables
Any variable declared globally becomes a property of the window
object and persists for the lifetime of the page.
var a = "apples"; // global
b = "oranges"; // also global (implicitly)
console.log(window.a); // "apples"
console.log(window.b); // "oranges"
Fix:
Encapsulate variables using block scope (let
or const
) or IIFEs.
(function(){
let a = "apples";
const b = "oranges";
})();
4. Limiting Object References
Avoid passing whole objects when only a small part is needed.
Avoid:
function printProp1(test){
console.log(test.prop1);
}
printProp1(test);
Better:
function printProp1(prop1){
console.log(prop1);
}
printProp1(test.prop1);
This helps prevent unintentional retention of the entire object.
5. The delete
Operator
Use delete
to remove unnecessary object properties:
var obj = { foo: 'bar' };
delete obj.foo;
This can help reduce memory usage, especially when working with large dynamic data structures.
6. Avoid Detached DOM Nodes
A detached DOM node is a DOM element no longer in the document tree but still referenced in JavaScript.
Bad:
var detached = document.getElementById('someElement');
document.body.removeChild(detached);
// `detached` is still in memory
Fix:
Set it to null
after removal:
document.body.removeChild(detached);
detached = null;
7. Timers and Intervals
Forgotten setTimeout
or setInterval
callbacks can cause leaks if they reference large data or DOM.
Bad:
setInterval(function() {
console.log(document.getElementById("something"));
}, 1000);
Fix:
- Always clear intervals/timeouts:
let id = setInterval(fn, 1000);
// Later
clearInterval(id);
- Avoid referencing DOM in long-running timers.
8. Closures and Callbacks
Closures retain the entire lexical scope, which can unintentionally preserve objects.
Bad:
function outer() {
let largeArray = new Array(100000).fill(0);
return function inner() {
console.log(largeArray[0]);
};
}
Fix: Extract Only What You Need
Instead of closing over the entire large structure, close over only the minimal data required.
function createCallback() {
let largeArray = new Array(1000000).fill('memory');
let neededValue = largeArray[0]; // Extract only what's needed
return function() {
console.log(neededValue); // Closure retains only a small string
};
}
let callback = createCallback(); // largeArray is now garbage-collected
- largeArray becomes eligible for garbage collection as soon as createCallback() finishes.
- The inner function retains only a primitive string, not the whole array.
9. Event Listeners
Unremoved event listeners are a common cause of leaks, especially in Single Page Applications.
Best Practice:
- Always remove listeners when elements are removed:
element.addEventListener('click', handler);
// ...
element.removeEventListener('click', handler);
- Use
{ once: true }
for auto-removal:
element.addEventListener('click', handler, { once: true });
10. Tools to Detect Memory Leaks
-
Chrome DevTools
- Memory Tab: Take snapshots and compare.
- Performance Tab: Record memory usage over time.
- Heap Snapshots: Identify detached nodes and retained memory.
- Performance Profilers: Identify high memory-consuming functions.
- Third-party tools: LeakCanary (Android), Firefox Profiler, ESLint plugins for detecting bad patterns.
Conclusion
Memory leaks are silent but deadly. Preventing them requires:
- Avoiding global variables.
- Removing unused DOM references and event listeners.
- Being mindful of closures and long-living timers.
- Breaking object references when no longer needed.
By applying these techniques, you can keep your JavaScript apps performant and robust over time.
Top comments (0)