Frontend Labs
⋯

50. Garbage collection and memory leaks in real JS apps

V8 uses a generational garbage collector, based on the empirical observation that most objects die young: two heap generations, collected with different strategies.

Young generation (small, "Scavenger" GC — fast, frequent)
  - new objects allocated here first
  - most die almost immediately (a temporary object in a function call) ->
    cheap to collect because most of the generation IS garbage
  - objects that SURVIVE a couple of scavenges get PROMOTED to:

Old generation (large, "Mark-Sweep-Compact" GC — slower, less frequent)
  - long-lived objects: app state, caches, closures held for a page's lifetime
  - collected less often since triggering a full mark-sweep is expensive

Memory leaks in JS aren't "forgotten free()" bugs (there's no manual free) — they're references you didn't mean to keep, which prevent the GC from ever considering an object garbage. The recurring real-world patterns:

// 1. Detached DOM nodes: removing an element from the document but keeping
//    a JS reference to it anywhere (a cache, an event-handler closure) keeps
//    its ENTIRE subtree alive in memory even though it's invisible.
const detachedCache = [];
function removeAndLeak(el) {
  el.remove();               // gone from the visible DOM...
  detachedCache.push(el);    // ...but still alive in memory via this array
}

// 2. Forgotten event listeners / timers holding closures over large state
function setupLeak(largeData) {
  window.addEventListener("resize", () => console.log(largeData.length));
  // if this component is ever "destroyed" without removeEventListener,
  // `largeData` is held alive forever by the still-registered listener
}

// 3. Growing caches/maps with no eviction — a Map that only ever grows
//    is a slow, "successful" memory leak by design, just not called one
const cache = new Map();
function memoize(key, computeFn) {
  if (!cache.has(key)) cache.set(key, computeFn());   // never evicted -> unbounded growth
  return cache.get(key);
}

Diagnosing leaks with DevTools: Memory panel → take a heap snapshot → perform the suspected leaking action several times → take another snapshot → use "Comparison" view to see which object types grew between snapshots that shouldn't have — a detached HTMLDivElement count climbing steadily is the single most common finding in real leak hunts.

Generated from real-life-JS-guide.md · 8 Oct 2026 16:36