What is the garbage collector (Garbage Collector)? Explain in detail how it is structured
What the garbage collector (GC) is
The garbage collector is part of the runtime (V8 in Chrome/Node, SpiderMonkey in Firefox, and so on) that automatically frees memory occupied by objects that can no longer be reached from the program's roots.
The main criterion: reachability. If an object is reachable from the roots (global objects, the current call stack, registers, active closures, the DOM tree, and so on), it is alive. If it is not reachable, its memory can be freed.
The basic algorithm: mark-and-sweep
- Mark: walk the object graph from the roots, marking everything reachable.
- Sweep: walk the heap and free everything unmarked. Additionally, compaction may be performed - moving surviving objects to eliminate memory fragmentation.
Generational GC
An observation: "most objects live for a short time." Hence:
- Young generation (nursery/new space): new objects end up here; frequent, fast collections (minor GC / scavenging).
- Old generation (tenured/old space): objects that survive several minor collections are promoted here; collected less often, but more expensively (major GC).
- Technically implemented via:
- bump-pointer allocation (very fast sequential allocation),
- copying GC for the young generation (two semi-spaces: from-space -> to-space),
- mark-(sweep|compact) for the old generation.
For the young and old generations to correctly reference each other, the runtime uses:
- write barriers + card marking / remembered sets - a cheap way of recording metadata when creating references "from old to young", so the minor GC knows which old objects may hold young ones.
Incrementality, concurrency, and tri-color marking
A full "stop-the-world" pause of significant length is unacceptable in a UI or a server, so the following are used:
- Tri-color marking (white/gray/black): a formal model of marking that allows the graph traversal to advance incrementally.
- Incremental GC: marking is split into small chunks, and the application keeps running between them.
- Concurrent GC: part of the work (for example, marking) runs in parallel with the application on another thread.
- Incremental/parallel marking & sweeping, compaction with minimized pauses.
The idea: reduce jank/freezes by using small, predictable pauses.
Why not "reference counting"
Reference counters break on cycles (A->B->A), which leads to leaks. That's why JS uses tracing GCs (mark-and-sweep/compact), where a cycle is not a problem: if a whole component is unreachable, it will be collected.
Weak references and finalization
- WeakMap/WeakSet: keys (for WeakMap) and elements (in WeakSet) do not prevent collection. As soon as an object is no longer reachable anywhere, GC may free it, and the entry disappears "by itself". This is useful for caches/memoization without leaks.
- FinalizationRegistry: lets you run a callback after an object has been collected. Important warnings: the timing of the call is not deterministic, the order is not guaranteed, and the call may not happen at all (when the process ends). You cannot rely on it for logic; only for "soft" side effects (cleaning up external caches, and so on).
Example of a safe cache:
const cache = new WeakMap(); // key - the object, value - the computation
function heavy(obj) {
if (cache.has(obj)) return cache.get(obj);
const val = compute(obj);
cache.set(obj, val);
return val;
}What this looks like specifically in V8 (Chrome/Node)
- The heap is split into spaces: new space (the semi-spaces of the copying GC), old space (mark-sweep/compact), code space, map space, large object space (no moving).
- Minor GC (Scavenger): quickly copies live objects from from-space to to-space; objects that survive N times are promoted to old space.
- Major GC: precise marking, then sweep/compact; incremental and concurrent, to reduce pauses.
- Details: write barriers, remembered sets, parallel marking/sweeping, heuristics based on memory pressure and fragmentation.
When GC runs
- No free space for a new allocation (allocation failure).
- Memory pressure from the OS/host.
- Heuristics based on heap growth and the frequency of minor collections.
- It can be triggered manually in DevTools (for debugging only).
In practice: how not to work against GC
Typical sources of leaks in JS applications:
- Long-lived references: caches, singletons, closures, global structures, retained DOM nodes.
- Events/timers: an unremoved handler or
setInterval; danglingPromisereferences. - DOM<->JS cycles: holding a reference to a node removed from the DOM, or its closure.
- Unbounded structures: a Map/Array that grows without cleanup.
What to do:
-
Remove event handlers and timers:
javascriptconst handler = () => {}; el.addEventListener('click', handler); // ... el.removeEventListener('click', handler); -
For caches tied to objects, use WeakMap/WeakSet.
-
Avoid unnecessary global/module-level references.
-
Be careful with closures: don't close over extra large objects.
-
Bound cache sizes (LRU) and clear them.
-
In React, watch the cleanup in
useEffect.
Diagnostic tools
- Chrome DevTools -> Performance/Memory: Heap snapshot, Allocation instrumentation -> look for "Detached HTML nodes", growing retainers.
performance.memory(browser, experimental) - rough telemetry.- Node.js:
--inspect, heap snapshots,clinic/heapdump, profilers, the--max-old-space-sizeflags. - Test idea: run the scenario N times -> memory should stabilize. Constant growth is a sign of a leak.
Important properties and limitations
- GC is not deterministic: you don't control the exact time of a collection.
- Finalizers are not guaranteed to run by the time the process/tab exits.
- Large pauses are still possible, but modern GCs strive to keep them short.
Short summary
GC removes unreachable objects. Modern implementations use a generational, incremental, concurrent mark-and-(sweep/compact) approach, reducing pauses. For a developer, the key is to manage references (listeners, timers, caches, closures, DOM) and to diagnose memory behavior using profiling tools.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.