How does garbage collection work in JavaScript?
Advertisement
🧩 Scenario
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
| Mechanism | Strength | Limitation |
|---|---|---|
| Mark-and-sweep | Handles cycles, no reference count overhead | Non-deterministic timing, cannot guarantee immediate collection |
| Generational GC | Fast short-lived object collection | Long-lived object promotion increases major GC cost |
| WeakMap / WeakRef | GC-friendly caching without preventing collection | No 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
WeakMapdiffers fromMapin terms of GC behavior
Follow-Up Questions
- What is
WeakRefand how does it differ fromWeakMapin terms of GC interaction? - How can you use Chrome DevTools Memory panel to take a heap snapshot and identify what is keeping an object alive?
- What does the GC do with detached DOM nodes that are still referenced from JavaScript variables?
- Why does V8 use a Scavenge algorithm for the young generation rather than full mark-and-sweep?
- If you store a function reference in a global variable, what objects does the GC consider reachable through it?
Common Candidate Mistakes
- Thinking
user = nullimmediately 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.
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