Memory Management & Garbage Collection
Key Learning Objectives
Understand the JavaScript memory lifecycle: Allocation, Usage, and Release (Garbage Collection).
Master the Reachability principle and how root references (global, call stack, DOM roots) keep heap objects alive.
Understand how modern V8 Mark-and-Sweep and Generational collectors handle circular references without leaking memory.
Identify common memory leak patterns: forgotten timers, accidental global variables, detached DOM trees, and retained closures.
Distinguish formal language specification guarantees from engine-specific heuristic collector timing (why GC cannot be scheduled by user code).
The Interview Problem
What is logged to the console when the following object reference graph is mutated, and why do circular references remain reachable as long as an active path from a root exists?
1let parent = { name: 'parent' };2let child = { name: 'child' };34// 1. Establish circular references5parent.child = child;6child.parent = parent;78// 2. Break root reference to parent9const childRef = parent.child;10parent = null;1112// 3. Inspect reachability through childRef13const res1 = childRef.name;14const res2 = childRef.parent ? childRef.parent.name : null;15const res3 = childRef.parent.child === childRef;1617console.log(res1, res2, res3);
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
child parent true
child null false
null null false
child parent false
V8 Engine Execution Trace
Step 1 of 6 (Line 1)Allocates two heap objects: parent (HeapRef#1) and child (HeapRef#2). Both variables exist as roots in the global environment.
Deep Technical Breakdown
The JavaScript Memory Model & Reachability
Memory in JavaScript engines (like Google Chrome and Node.js's V8) is divided into two primary zones:
- The Call Stack: Stores primitive variables and execution frames. Allocations and deallocations are rapid and tied strictly to function scope lifecycles (LIFO push/pop).
- The Memory Heap: Stores composite objects, arrays, and functions. Memory allocations are dynamic and unstructured.
The Reachability Principle
An object in heap memory is considered reachable if it can be accessed from a set of inherent roots:
- Currently executing local variables and parameters on the Call Stack.
- Global variables (
window,globalThis). - Active DOM trees connected to the document root.
If an object can be reached by traversing pointers starting from any root, it is kept in memory. If no retaining path from any root exists, the object becomes eligible for garbage collection.
How V8 Reclaims Memory: Mark-and-Sweep
Older engines (like Internet Explorer 6/7) used naive Reference Counting, which failed catastrophically with circular references. Modern engines use Mark-and-Sweep:
- Mark Phase: The collector starts at all known roots and traverses every outgoing pointer, marking visited objects as 'alive'.
- Sweep Phase: The collector scans the entire heap. Any un-marked memory is freed and added to the free-list.
- Compact Phase: Surviving objects are compacted to prevent memory fragmentation.
Because Mark-and-Sweep traces reachability from roots, two objects that reference each other circularly will be completely freed as soon as their connection to the roots is severed!
The 4 Most Dangerous Frontend Memory Leak Patterns
- Accidental Global Variables: Assigning to undeclared variables (
foo = 'bar') attaches them permanently towindow. - Forgotten Timers & Intervals:
setInterval(() => { ... })retains all variables in its closure environment indefinitely untilclearIntervalis called. - Detached DOM Nodes: Storing a DOM node reference in a JavaScript array or object after
node.remove()has pulled it from the document. The entire subtree remains retained in memory. - Retained Closures: Large objects captured in an outer function scope where an inner function persists (e.g. as a global callback or event listener).
Language Guarantees vs Engine Heuristics: Nondeterministic GC
Critical Interview Fact: The ECMAScript specification defines zero timing guarantees for garbage collection. Garbage collection is an engine-level background heuristic, never a guaranteed runtime event. Setting an object reference to null does not trigger an immediate collection; it merely severs a retaining path. Application code cannot trigger, predict, or measure exact garbage collection timings. Methods like global.gc() exist exclusively in Node.js test harnesses when launched with --expose-gc.
Common Traps & Mistakes
Assuming setting `obj = null` immediately frees the object. Memory is only reclaimed during subsequent engine GC cycles, and only if no other retaining references exist.
Believing circular references cause memory leaks in modern browsers. Modern Mark-and-Sweep collectors handle circular references cleanly as long as the cycle is disconnected from all roots.
Forgetting to unsubscribe from event listeners or abort `AbortController` instances when unmounting UI components.
Claiming that JavaScript code can programmatically force the browser to run garbage collection.
FAANG Follow-Up Probes
Probe #1
What is Generational Garbage Collection, and why is the V8 heap split into the Young Generation (Nursery/Intermediate) and Old Generation?
Probe #2
What is the difference between Shallow Size and Retained Size when inspecting a Chrome DevTools Heap Snapshot?
Probe #3
How does FinalizationRegistry in ES2021 allow tracking when an object is collected, and why should it never be used for critical program logic?
