Back to Home

Memory leaks in JS closures: how to fix

The article breaks down the mechanisms of memory leaks in JavaScript closures with code examples and DevTools. Solutions described: resetting references, removing handlers, WeakMap with weak references. Practical diagnostics instructions.

Memory leaks in JS closures: WeakMap and solutions
Advertisement 728x90

How to Avoid Memory Leaks in JavaScript Closures

JavaScript closures keep objects alive in memory as long as there's a reference to the closure function. This causes leaks when an object is no longer needed, but the garbage collector can't free it because it's captured in the closure.

Consider a typical case with a large object:

let bigData = {
  id: 42,
  data: new Array(1000000).fill('Some data')
}

function handler(data) {
  return function() {
    console.log(data.id);
    debugger;
  }
}

const runHandler = handler(bigData);
bigData = null;
runHandler();

The inner function only uses data.id, but the closure captures the entire bigData object. In DevTools at the breakpoint, you can see the object still lingers in memory.

Google AdInline article slot

Issues with eval() in Closures

The eval() function worsens leaks because the engine adds all outer variables to the closure, regardless of whether they're used:

let hello = 'Hello';
let userName = 'Vasya';

setTimeout(function () {
  debugger;
  return eval('console.log("Hello from Vasya!")');
}, 1000);

In DevTools, the closure includes hello and userName, even though eval() doesn't reference them. This happens due to the uncertainty of code inside eval(). Avoid eval() in production.

Methods to Prevent Leaks

Force Reset Data

Reset captured data after use:

Google AdInline article slot
function runCalc(buttonId) {
  let bigData = new Array(1000000).fill('Something');
  const button = document.getElementById(buttonId);

  button.addEventListener('click', function onClick() {
    console.log('Processed items:', bigData.length);
    bigData = null;
  });
}

Assigning null releases the reference inside the closure, allowing the garbage collector to remove the object.

Properly Remove Event Handlers

Event handlers are a common leak source when removing DOM elements:

function clickSetup(btn, message) {
  const bigData = new Array(1000000).fill(message);

  function handler() {
    console.log(message, bigData.length);
    btn.remove();
    btn.removeEventListener('click', handler);
  }

  btn.addEventListener('click', handler);
}

A named function handler can be removed along with the element. Anonymous arrow functions can't be removed this way.

Google AdInline article slot

WeakMap for Weak References

WeakMap solves the issue of capturing large objects by using weak references to object keys.

Keys Must Be Objects

const weak = new WeakMap();
const key = {};
weak.set(key, 'Vasya was here!');
console.log(weak.get(key));
const anotherKey = {};
console.log(weak.get(anotherKey)); // undefined

Keys must be objects or non-primitives. Access via get(key) by reference.

Weak References and Garbage Collection

References in WeakMap don't prevent garbage collection:

const weak = new WeakMap();
let userKey1 = {id: 1};
weak.set(userKey1, 'Vasya');
userKey1 = null;

After nulling userKey1, the object and value are garbage collected.

No Iterators

No size, forEach, keys(). Only basic methods:

  • set(key, value) — set
  • get(key) — get
  • has(key) — check
  • delete(key) — delete

No iterators to avoid unpredictability during garbage collection.

Using WeakMap in Closures

A closure with WeakMap doesn't hold objects strongly:

function createProcessor() {
  const weakMap = new WeakMap();

  return function process(obj) {
    weakMap.set(obj, obj.value);
    debugger;
    return weakMap.get(obj);
  }
}

const processor = createProcessor();

let bigData = {
  value: 21,
  hugeArray: new Array(1000000).fill('*')
};

console.log(processor(bigData));
bigData = null;

let bigData2 = {
  value: 42,
  hugeArray: new Array(1000000).fill('1')
};

console.log(processor(bigData2));

In DevTools after garbage collection, only bigData2 remains. WeakMap enables automatic memory cleanup.

To verify:

  • Set a breakpoint in process().
  • Step through code (F10).
  • After bigData = null, switch to Memory tab.
  • Trigger garbage collection with the button.
  • Continue — only current keys remain.

Key Takeaways

  • Closures capture entire objects, even if only one property is used.
  • eval() adds all outer variables to the closure.
  • Named event handlers can be removed via removeEventListener.
  • WeakMap uses weak references to object keys.
  • No iterators in WeakMap for predictable garbage collection.

— Editorial Team

Advertisement 728x90

Read Next