🧠
Advanced
12 min read

Memory Management & Garbage Collection

Understanding memory leaks, heap, stack, and garbage collection in JavaScript

Memory Management & Garbage Collection

Understanding how JavaScript manages memory is crucial for building performant applications. Learn about the heap, stack, garbage collection, and how to prevent memory leaks.

1. Stack vs Heap Memory

Stack Memory:
  • Stores primitive values (numbers, strings, booleans)
  • Stores function call frames and local variables
  • Fast access, automatic cleanup when function returns
  • Limited size (typically 1MB)
  • LIFO (Last In, First Out) structure
Heap Memory:
  • Stores objects, arrays, and functions
  • Larger size, slower access than stack
  • Requires garbage collection for cleanup
  • Variables hold references to heap locations
// Stack memory
let x = 10; // Primitive stored on stack
let y = 20;
let sum = x + y; // Result on stack

// Heap memory
let obj = { name: 'John', age: 30 }; // Object on heap
let arr = [1, 2, 3]; // Array on heap
// obj and arr variables (references) are on stack

// Reference example
let a = { value: 5 };
let b = a; // b references same object
b.value = 10;
console.log(a.value); // 10 - both reference same object

2. Garbage Collection

JavaScript uses automatic garbage collection to free memory. The two main algorithms:

Mark-and-Sweep Algorithm (modern approach):

// Root objects (global, currently executing functions)
// are marked as "active"
// GC traverses and marks all reachable objects
// Unreachable objects are swept (deleted)

function createUser() {
  let user = { name: 'John' }; // Created
  return user;
}

let activeUser = createUser(); // user object is reachable
activeUser = null; // Now unreachable, will be garbage collected

// Example: Circular references (handled by mark-and-sweep)
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
obj1 = null;
obj2 = null;
// Both objects are now unreachable and will be GC'd

3. Common Memory Leaks

Leak #1: Global Variables

// āŒ Bad: Accidental global
function leak() {
  user = { name: 'John' }; // No let/const, becomes global
}

// āœ… Good: Use strict mode
'use strict';
function noLeak() {
  let user = { name: 'John' }; // Properly scoped
}

Leak #2: Event Listeners

// āŒ Bad: Listener never removed
function setupListener() {
  const button = document.querySelector('#btn');
  button.addEventListener('click', () => {
    console.log('Clicked');
  });
}

// āœ… Good: Remove listener
function setupListener() {
  const button = document.querySelector('#btn');
  const handler = () => console.log('Clicked');
  button.addEventListener('click', handler);
  
  // Cleanup
  return () => button.removeEventListener('click', handler);
}

// React example
useEffect(() => {
  const handler = () => console.log('Scroll');
  window.addEventListener('scroll', handler);
  
  return () => window.removeEventListener('scroll', handler);
}, []);

Leak #3: Timers

// āŒ Bad: Timer never cleared
function startTimer() {
  setInterval(() => {
    console.log('Tick');
  }, 1000);
}

// āœ… Good: Clear timer
function startTimer() {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);
  
  return () => clearInterval(timerId);
}

// React example
useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);
  
  return () => clearInterval(timerId);
}, []);

Leak #4: Closures Holding References

// āŒ Bad: Closure holds large object
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  
  return function() {
    console.log(largeData[0]); // Holds entire array
  };
}

// āœ… Good: Extract only what you need
function createHandler() {
  const largeData = new Array(1000000).fill('data');
  const firstItem = largeData[0];
  
  return function() {
    console.log(firstItem); // Only holds one item
  };
}

Leak #5: Detached DOM Elements

// āŒ Bad: Keeping reference to removed DOM element
let elements = [];

function addElement() {
  const div = document.createElement('div');
  document.body.appendChild(div);
  elements.push(div); // Keep reference
}

function removeElement() {
  document.body.removeChild(elements[0]);
  // Element removed from DOM but still in memory (elements array)
}

// āœ… Good: Remove all references
function removeElement() {
  const element = elements.shift();
  document.body.removeChild(element);
  // No more references, can be GC'd
}

4. WeakMap and WeakSet

Use WeakMap and WeakSet for objects you don't want to prevent from being garbage collected.

// Regular Map prevents GC
const map = new Map();
let obj = { data: 'important' };
map.set(obj, 'metadata');
obj = null; // Object NOT garbage collected (Map still holds it)

// WeakMap allows GC
const weakMap = new WeakMap();
let obj2 = { data: 'important' };
weakMap.set(obj2, 'metadata');
obj2 = null; // Object CAN be garbage collected

// Practical use: Private data
const privateData = new WeakMap();

class User {
  constructor(name) {
    privateData.set(this, { password: 'secret' });
    this.name = name;
  }
  
  getPassword() {
    return privateData.get(this).password;
  }
}

const user = new User('John');
console.log(user.name); // Accessible
console.log(user.password); // undefined (private)
console.log(user.getPassword()); // 'secret'

5. Memory Profiling

// Check memory usage
if (performance.memory) {
  console.log('Heap size:', performance.memory.totalJSHeapSize);
  console.log('Used heap:', performance.memory.usedJSHeapSize);
  console.log('Heap limit:', performance.memory.jsHeapSizeLimit);
}

// Chrome DevTools:
// 1. Open DevTools → Memory tab
// 2. Take heap snapshot
// 3. Compare snapshots to find leaks
// 4. Look for detached DOM nodes
// 5. Check objects in memory

// Force garbage collection (DevTools only)
// Performance → Memory → Collect garbage icon
Memory Optimization Best Practices:
  • āœ“ Always cleanup event listeners, timers, and subscriptions
  • āœ“ Avoid global variables
  • āœ“ Be careful with closures - don't capture unnecessary data
  • āœ“ Remove references to DOM elements when removing them
  • āœ“ Use WeakMap/WeakSet for cached data tied to objects
  • āœ“ Profile memory regularly during development
  • āœ“ Set large objects to null when done
  • āœ“ Use object pooling for frequently created/destroyed objects