Closures & Memory Management

Hard

Closures keep references to outer scope variables alive as long as the closure exists. This can lead to memory leaks if closures unintentionally hold large objects or DOM references that are no longer needed. Understanding when and how to release closures is important for long-running applications.

Interactive Visualization

CodeCreation Phase
1function outer() {
2 let x = 10;
3 function inner() {
4 return x;
5 }
6 return inner;
7}
8
9const fn = outer();
10fn(); // 10
Call Stack
Global EC
Variables:
fnundefined
Heap Memory
(empty)
Output
Global EC created - outer function is defined
1 / 6
Key Insight: A closure is formed when an inner function is returned from an outer function, maintaining access to the outer function's variables even after the outer function has completed.

Key Points

  • Closures keep outer scope variables alive
  • Large objects captured in closures cannot be garbage collected
  • Event listeners with closures can hold DOM references
  • Remove event listeners when no longer needed
  • Set variables to null when done to allow GC
  • Use weak references (WeakMap/WeakSet) for cache-like structures

Code Examples

Accidental Large Object Retention

function processLargeData(data) {
  const hugeData = loadHugeDataset(); // 100MB
  
  return {
    getSmallResult() {
      // Only uses small part of hugeData
      return data.summary;
    }
  };
}

const result = processLargeData({ summary: "Done" });

// Problem: hugeData (100MB) is kept in memory
// because the closure in getSmallResult references it
// Even though it only uses data.summary!

// Fix: Only capture what you need
function processLargeDataFixed(data) {
  const hugeData = loadHugeDataset();
  const summary = hugeData.summary; // Extract what you need
  
  return {
    getSmallResult() {
      return summary; // Only captures summary, not hugeData
    }
  };
}

Closures capture the entire outer scope. Extract only what you need to avoid retaining large objects.

Event Listener Leak

function setupHandler() {
  const element = document.getElementById('button');
  const largeData = fetchLargeData();
  
  element.addEventListener('click', function handler() {
    console.log(largeData); // Closure captures largeData
  });
}

// Problem: Even if element is removed from DOM,
// the event listener keeps both element and largeData in memory!

// Fix: Remove listener when done
function setupHandlerFixed() {
  const element = document.getElementById('button');
  const largeData = fetchLargeData();
  
  function handler() {
    console.log(largeData);
  }
  
  element.addEventListener('click', handler);
  
  // Cleanup function
  return function cleanup() {
    element.removeEventListener('click', handler);
  };
}

const cleanup = setupHandlerFixed();
// Later...
cleanup(); // Releases closure and allows GC

Event listeners with closures keep references alive. Always remove listeners when components are destroyed.

Common Mistakes

  • Creating closures that capture large unnecessary objects
  • Not removing event listeners in SPAs when components unmount
  • Using regular Map/Object for caches that should use WeakMap

Interview Tips

  • Know that closures prevent garbage collection of captured variables
  • Understand the event listener memory leak pattern
  • Know about WeakMap/WeakSet for avoiding leaks
  • Show awareness of cleanup in component lifecycles