Tags: web-dev concept

Garbage Collection

Date: 2026-08-17


Automatic reclamation of memory nothing can reach any more. It removes the whole category of manual-free bugs and replaces it with a subtler one — memory you no longer want but are still holding a reference to, which the collector is obliged to keep.


Garbage collection (GC) frees memory occupied by objects that are no longer reachable from any root — a global, a variable in a live function call, or a closure holding one.

Reachability, not usefulness. The collector cannot know you’ve finished with something; it only knows whether a path to it still exists.

How it decides

ROOTS
├─ globals
├─ the call stack
└─ closures currently alive
      │
      ▼  follow every reference
  reachable objects  → keep
  everything else    → free

This is mark and sweep: walk from the roots marking what’s reachable, then free the unmarked. Because it works from reachability rather than counts, it handles cycles correctly — two objects referring only to each other are unreachable and get collected. Naive reference counting cannot do this, which is why it isn’t used alone.

Generational collection

The optimisation every real collector uses, based on one observation: most objects die young.

NEW SPACE (nursery)        OLD SPACE
short-lived objects        survivors
collected often, fast      collected rarely,
                           slowly

allocate → survive a few → promoted
           collections

Minor collections scan only the nursery and are quick — often under a millisecond. Major collections scan everything and are the ones that show up as jank.

The practical consequence: allocating many short-lived objects is cheap. Allocating many objects that survive is expensive, because they get promoted and make every major collection slower.

What it costs you

  • Pauses. Collection has to stop the program at some point. Modern collectors are incremental and concurrent, so pauses are short — but in a browser, a major GC during an animation is a dropped frame — Interaction to Next Paint
  • Unpredictability. You don’t control when it runs
  • Memory overhead. A collected heap runs larger than a manually managed one

The leaks GC does not prevent

A leak in a managed language is a reference you forgot you were holding. The collector is behaving correctly; you asked it to keep the object.

Detached DOM nodes — removed from the document, still referenced, so the whole subtree stays in memory.

const cache = []
cache.push(document.querySelector('#row'))
row.remove()

Unremoved listeners — the element goes, the listener never does, and the handler’s closure holds everything it captured.

el.addEventListener('click', handler)
// handler closes over a large object

Timers — never cleared, so tick and everything it closes over live forever.

setInterval(tick, 1000)

Growing caches — unbounded, and keyed by user input.

const memo = {}
memo[key] = result

The pattern is identical each time: something long-lived holds a reference to something short-lived. Component teardown, removeEventListener, clearInterval, and a bounded cache are the four fixes — Memory Models.

Weak references

The escape hatch when you need to hold a reference without preventing collection.

Map        holds the key alive
WeakMap    does NOT
const meta = new WeakMap()
meta.set(element, { seen: true })
// element removed from the DOM → both it
// and its metadata become collectable

WeakMap and WeakSet are the right tool for attaching data to DOM nodes or other objects you don’t own. Keys must be objects, and they aren’t enumerable — deliberately, since the contents can change at any moment.

Finding a leak

1  DevTools → Memory → heap snapshot
2  perform the suspected action
3  force collection, take a second snapshot
4  compare — what grew and didn't shrink?
5  follow the RETAINER path to the root

The retainer path is the answer, not the object count. It names exactly what is still holding the thing, which is the reference to remove.

A steadily climbing heap across repeated identical actions is the signature. Steady-state fluctuation is normal and healthy.