How does garbage collection work in JavaScript?

Advanced14 min interview
Skills tested:
Mark-and-sweep algorithm and reachability conceptWhat counts as a root in the GC root setGenerational GC in V8 and why most objects die youngHow circular references are handled in modern enginesWhat keeps an object alive unintentionally and how to detect it

Advertisement

🧩 Scenario

In a real codebase, garbage collection is the silent background process that keeps memory usage stable. You do not control when it runs, but you do control what stays reachable. Understanding reachability explains why event listeners, closures over large objects, and references stored in global caches cause memory to grow rather than shrink over time.

Architecture Walkthrough

Reachability and GC Roots

JavaScript uses a reachability-based garbage collector. An object is considered alive if it is reachable by traversing references starting from the GC roots. If no path from any root leads to an object, the object is garbage and eligible for collection.

The GC roots in a browser are the global object (window), the current call stack (all local variables in active function calls), and any objects held by the JavaScript engine itself. Every object reachable from these roots is marked as live. Everything not marked is swept and its memory is freed.

Mark-and-Sweep Algorithm

The mark-and-sweep algorithm runs in two phases. In the mark phase, the engine starts from all roots and follows every object reference recursively, marking each visited object. In the sweep phase, the engine scans the heap and frees any object that was not marked. This approach correctly handles circular references: two objects that reference each other but are unreachable from any root will both be unmarked and both will be collected.

Modern engines do not pause execution for an entire collection. Incremental, concurrent, and parallel GC techniques break the work into smaller chunks so that collection does not cause a noticeable freeze in the application.

Generational Collection in V8

V8 (the engine powering Chrome and Node.js) divides the heap into two generations. New objects are allocated in the young generation (called the nursery), which is small and collected frequently using a fast algorithm called Scavenge. Objects that survive multiple young-generation collections are promoted to the old generation, which is collected less frequently with a more expensive major GC.

Most objects die young: they are created during a single user interaction, used briefly, and become unreachable shortly after. This empirical observation is why the generational approach is so effective in practice. Understanding it also explains why short-lived objects (temporary render results, intermediate computation values) do not degrade memory performance over time.


Key Code Explained

// Object becomes eligible for GC when the only reference is removed
let user = { name: 'Ghazi', sessions: new Array(10_000).fill('data') };
user = null; // the object is now unreachable from any root

// Circular references: not a leak in modern engines
function createCycle() {
  const a = {};
  const b = {};
  a.ref = b; // a references b
  b.ref = a; // b references a
  // Neither a nor b is reachable outside this function
  // Both are collected when createCycle returns
}
createCycle();

// What DOES prevent collection: storing a reference in a long-lived scope
const cache = new Map();

function processUser(user) {
  cache.set(user.id, user); // user stays alive as long as cache exists
  // If cache is a module-level variable, user will never be collected
}

// WeakMap allows collection when the key has no other references
const metadata = new WeakMap();

function processNode(node) {
  metadata.set(node, { visitedAt: Date.now() });
  // When node is removed from the DOM and no other reference holds it,
  // the GC can collect both node and its metadata entry
}

The Map vs WeakMap contrast is the most practical GC-related design decision in application code. A Map with object keys holds strong references and prevents collection. A WeakMap with object keys holds weak references that do not prevent the GC from collecting the key object.


Tradeoffs

MechanismStrengthLimitation
Mark-and-sweepHandles cycles, no reference count overheadNon-deterministic timing, cannot guarantee immediate collection
Generational GCFast short-lived object collectionLong-lived object promotion increases major GC cost
WeakMap / WeakRefGC-friendly caching without preventing collectionNo enumeration, non-deterministic when entry is removed

What Interviewers Actually Check

  • Whether you can explain mark-and-sweep starting from GC roots
  • Whether you know that circular references are not a leak in modern engines
  • Whether you know what keeps an object alive (a reachable reference, not just "a reference")
  • Whether you can explain generational GC at a high level
  • Whether you know how WeakMap differs from Map in terms of GC behavior

Follow-Up Questions

  1. What is WeakRef and how does it differ from WeakMap in terms of GC interaction?
  2. How can you use Chrome DevTools Memory panel to take a heap snapshot and identify what is keeping an object alive?
  3. What does the GC do with detached DOM nodes that are still referenced from JavaScript variables?
  4. Why does V8 use a Scavenge algorithm for the young generation rather than full mark-and-sweep?
  5. If you store a function reference in a global variable, what objects does the GC consider reachable through it?

Common Candidate Mistakes

  • Thinking user = null immediately frees the object when GC timing is non-deterministic and may run much later
  • Claiming circular references always cause memory leaks when modern mark-and-sweep handles them correctly
  • Not knowing what the GC roots are and saying "it collects everything that has no references" without understanding what counts as a root
  • Confusing old reference-counting engines (which did leak on cycles) with modern mark-and-sweep engines
  • Not knowing about generational collection and being unable to explain why short-lived objects do not accumulate

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain mark-and-sweep in plain terms starting from GC roots?
  • Can you name the GC roots in a browser context?
  • Can you explain why circular references do not cause leaks in modern engines?
  • Can you describe the young and old generation in V8 and what Scavenge vs major GC means?
  • Can you name three patterns that keep objects alive unintentionally?

Summary

JavaScript uses a mark-and-sweep garbage collector that considers an object alive if and only if it is reachable by following references from the GC roots. The roots include the global object, the active call stack, and engine-internal references. Objects that cannot be reached from any root are unreachable and will be freed the next time the GC runs.

Circular references are not a problem in mark-and-sweep. Two objects that only reference each other but have no path from any root are both unreachable and are both collected. The old reference-counting engines of the 1990s did leak on cycles, but modern engines do not.

V8 uses a generational approach: most objects are allocated in a small young generation and collected very quickly with a cheap Scavenge algorithm. Only objects that survive long enough are promoted to the old generation and participate in the more expensive major GC. Understanding reachability is the key skill: you do not control when the GC runs, but you do control which objects remain reachable.

Related Videos
Watch these videos to learn more about this topic
Frequently Asked Questions

Can I manually force garbage collection?

In browsers, no. JavaScript engines decide when to collect. In Node.js, there is a debug flag but you should not rely on it in production.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement