DEV Community

ZeeshanAli-0704
ZeeshanAli-0704

Posted on

JavaScript Memory Management

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]);
}
Enter fullscreen mode Exit fullscreen mode

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]);
}
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

Fix 1 – Query inside the event:

var one = document.getElementById("one");

one.addEventListener('click', function(){
    var two = document.getElementById("two");
    if (two) two.remove();
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Fix:
Encapsulate variables using block scope (let or const) or IIFEs.

(function(){
    let a = "apples";
    const b = "oranges";
})();
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Better:

function printProp1(prop1){
    console.log(prop1);
}
printProp1(test.prop1);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Fix:
Set it to null after removal:

document.body.removeChild(detached);
detached = null;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Fix:

  • Always clear intervals/timeouts:
let id = setInterval(fn, 1000);
// Later
clearInterval(id);
Enter fullscreen mode Exit fullscreen mode
  • 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]);
    };
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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);
Enter fullscreen mode Exit fullscreen mode
  • Use { once: true } for auto-removal:
element.addEventListener('click', handler, { once: true });
Enter fullscreen mode Exit fullscreen mode

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)