Suggest an editImprove this articleRefine the answer for “What is garbage collector in JavaScript?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Garbage collector** automatically finds and frees memory for objects no longer reachable from the program's roots (global scope, call stack). ```javascript let user = { name: "Alice" }; user = null; // unreachable - eligible for GC on next cycle ``` V8 uses mark-and-sweep: trace all reachable objects from roots, reclaim the rest. Circular references are not a problem. Real leaks come from forgotten timers, event listeners, and unbounded globals. **Key:** setting a reference to `null` makes an object eligible. GC runs on its own schedule, not on demand.Shown above the full answer for quick recall.Answer (EN)Image**Garbage collector** is a built-in part of the JavaScript engine that automatically finds and frees memory used by objects no longer reachable from active code. ## Theory ### TL;DR - Think of it as a hotel housekeeper: she removes luggage only after confirming no guest holds a claim ticket to it. - No references from roots (globals, call stack) = the object is garbage. - V8 uses mark-and-sweep: trace everything reachable from roots, then reclaim the rest. - Circular references do not cause leaks in modern engines. Mark-and-sweep handles them. - You cannot force GC in production. Real leaks come from forgotten timers, detached DOM nodes, and growing globals. ### Quick example ```javascript let user = { name: "Alice" }; // memory allocated on the heap console.log(user.name); // "Alice" - object is reachable user = null; // last reference cut // { name: "Alice" } is now unreachable from any root // V8 will reclaim it on the next GC cycle console.log(typeof user); // "object" - null is still a value ``` After `user = null`, nothing in the program can reach that object. It becomes a candidate for collection. The engine decides when to actually free the memory. ### How mark-and-sweep works V8 starts from a set of **roots**: the global object, the current call stack, and active handles. From each root it traces every reference - properties, [closures](/questions/what-is-closure-in-javascript), prototype chains - and marks each reachable object. When the traversal finishes, every unmarked object gets reclaimed. The key insight is reachability, not reference count. A cycle between two objects means nothing if neither is reachable from a root. Both get collected. ```javascript function createCycle() { let a = {}; let b = {}; a.ref = b; b.ref = a; // cycle created } createCycle(); // a and b go out of scope here // neither is reachable from any root // V8 collects both - no leak ``` Old reference-counting engines had trouble with exactly this pattern. Mark-and-sweep does not. ### Generational GC and incremental marking V8 splits the heap into two spaces. **Young generation** holds newly allocated objects, most of which die quickly. V8 cleans this space with fast scavenge passes. Objects that survive a few rounds get promoted to **old generation**, where full mark-and-sweep runs less often. Since V8 7.4, marking runs incrementally. The engine pauses the main thread in small slices instead of one long stop-the-world pause. This reduces jank in UI-heavy apps. Node 22+ ships with Orinoco GC, which improves throughput for server workloads. From JS code you cannot observe any of this directly. But it explains why DevTools sometimes shows memory that looks free yet has not been reclaimed. ### Sources of memory leaks `setInterval` is the sneakiest leak in production. I once debugged a Node.js service growing 50 MB per hour, and the culprit was an interval inside a request handler that nobody had cleared. GC only collects what it cannot reach, so if you accidentally keep a reference alive, the object stays in memory indefinitely. Four common sources: - **Timers**: `setInterval` keeps its callback and every closure it references alive until you call `clearInterval`. - **Event listeners**: a listener on a DOM element holds a reference to its handler. Remove the element without removing the listener and both stay in memory. - **Globals**: `window.cache = []` is a root. Everything pushed into it never gets collected. Use [WeakMap](/questions/what-is-weakmap-in-javascript) for object-keyed caches instead. - **Detached DOM nodes**: removing an element from the DOM tree does not free it if a JS variable still holds a reference. ### Common mistakes **Mistake: thinking `delete` frees memory** ```javascript let obj = { prop: "value" }; delete obj.prop; // removes the property key // obj itself is still allocated - heap size unchanged ``` `delete` removes a key from an object. To make the object eligible for GC, remove the reference to it: `obj = null`. **Mistake: storing unbounded state in globals** ```javascript window.cache = []; function add() { window.cache.push(new Array(1_000_000)); } // cache is a root, grows forever ``` Scope the data inside a function or module, or use `WeakMap` for object-keyed caches so entries are released automatically. **Mistake: forgetting to remove event listeners** ```javascript element.addEventListener("click", handler); // element removed from DOM, but handler closure still alive ``` Call `removeEventListener` in cleanup code, or use `AbortController` to cancel multiple listeners at once. **Mistake: expecting immediate collection** ```javascript largeObj = null; // memory may still show as used for seconds in DevTools ``` Setting to `null` makes the object eligible. GC runs on its own schedule and does not respond to your code. Monitor trends in heap snapshots, not single measurements. ### Real-world usage - React: `useEffect` cleanup functions (`clearInterval`, `removeEventListener`) prevent closure leaks in dashboards and SPAs. - Node.js/Express: `WeakMap` for per-request caches lets entries be collected when the request object goes out of scope. - Lodash: uses `WeakMap` internally in `memoize` so cached results are released along with the original object. - Chrome DevTools Memory tab: take a heap snapshot before and after an action, then compare the Retained Size column to find what is holding memory. ### Follow-up questions **Q:** How does V8 detect that an object is unreachable? **A:** It starts from roots (global, call stack) and traverses all references. Objects not touched during the traversal are unreachable. That is the mark phase of mark-and-sweep. **Q:** Do circular references cause memory leaks in modern JS? **A:** No. Mark-and-sweep detects unreachable cycles by checking reachability from roots, not by counting references. This was a real problem for older reference-counting engines but not for V8. **Q:** How do you force GC during development? **A:** Run Node.js with `--expose-gc` and call `global.gc()`. Browsers have no equivalent in production. Chrome DevTools lets you trigger a collection manually from the Memory tab. **Q:** What is the difference between young and old generation? **A:** Young generation holds short-lived objects and is cleaned with fast scavenge passes. Old generation holds survivors and uses full mark-and-sweep. Most objects never leave young generation, which is why typical apps stay fast. **Q:** A Node.js service grows from 300 MB to 2 GB over 24 hours. How do you debug it? **A:** Take heap snapshots at intervals and compare the Retained Size column. Look at what is growing - likely an array, Map, or EventEmitter accumulating entries. Suspect `setInterval` callbacks and any global cache without a size limit. Add `process.memoryUsage()` to a metrics endpoint to confirm the trend before opening DevTools. ## Examples ### Basic: breaking a reference ```javascript function allocate() { let data = new Array(100_000).fill("x"); // large heap allocation return data; } let result = allocate(); // result holds the array result = null; // array is now unreachable, eligible for GC ``` After `result = null`, the last reference to the array is gone. The 100k-element allocation becomes garbage on the next collection pass. ### Intermediate: React cleanup preventing a leak ```javascript function Dashboard() { const [items, setItems] = useState([]); useEffect(() => { const id = setInterval(() => { setItems(prev => [...prev, { ts: Date.now() }]); }, 1000); return () => clearInterval(id); // cleanup on unmount }, []); return <div>{items.length} items loaded</div>; } ``` Without `clearInterval`, the interval keeps running after `Dashboard` unmounts. Each tick adds an entry to `items`, and the closure holds the component state alive. Heap grows until the page reloads. The cleanup function is what gives GC permission to do its job. ### Advanced: WeakMap for metadata without leaking ```javascript const metadata = new WeakMap(); function attachMeta(obj, info) { metadata.set(obj, info); } let request = { id: 42 }; attachMeta(request, { startTime: Date.now() }); request = null; // WeakMap does not prevent GC // when request is collected, its entry in metadata disappears automatically // a regular Map would hold the reference and cause a leak ``` `WeakMap` keys are held weakly. When `request` is set to `null` and no other reference exists, the object is collected and the corresponding `WeakMap` entry disappears with it. A regular `Map` would keep that object alive indefinitely, which is the exact leak pattern the [WeakMap](/questions/what-is-weakmap-in-javascript) was designed to prevent.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.