JavaScript Garbage Collection: Complete Guide
Garbage collection automatically frees memory your code no longer needs. Learn how reachability, reference counting, and generational collection work across JavaScript engines.
JavaScript manages memory automatically through garbage collection. You never call free or delete. When you create objects, arrays, or functions, the engine allocates memory for them.
When those values become unreachable, the garbage collector reclaims the memory.
The core idea is reachability. An object is reachable if it can be accessed from a root.
Roots include global variables, local variables on the call stack, and active closures. If an object is reachable, it stays alive. If the collector determines an object is not reachable from any root, it is garbage and its memory is freed.
How Reachability Works
Reachability is determined by tracing references from roots through the object graph. Every reference from a root to an object counts. Every reference from that object to another object counts.
The collector follows every chain of references and marks everything it finds.
let user = { name: "Alex" };
// user is a root. The object is reachable.
user = null;
// The object is now unreachable. GC will collect it.A more complex example shows how references keep objects alive. Multiple variables can point to the same object, and the object stays reachable as long as at least one reference exists.
let person = { name: "Alex" };
let employee = person;
// Two roots point to the same object.
person = null;
// The object is still reachable through employee.
// GC will not collect it.
employee = null;
// Now unreachable. GC can collect it.Objects can reference each other in cycles. Even if a group of objects references only each other and no root reaches any of them, they are all garbage. Modern collectors handle cycles correctly.
For how V8 implements this specifically, see /javascript/how-v8-garbage-collector-works-in-javascript.
Reference Counting vs Tracing
There are two fundamental approaches to garbage collection: reference counting and tracing. JavaScript engines use tracing, but it is worth understanding both.
| Reference Counting | Tracing (Mark-and-Sweep) | |
|---|---|---|
| How it works | Counts references to each object; collects when the count hits zero | Walks the object graph from roots and marks everything reachable |
| Handles cycles | No, two objects referencing only each other never reach zero | Yes, an unreachable cycle is simply never marked |
| Used by | Older systems, some reference-counted languages | Every modern JavaScript engine |
Tracing handles cycles correctly because if no root can reach a cycle, none of its objects get marked. This is why JavaScript engines universally chose tracing over reference counting.
JavaScript engines universally use tracing collectors. For a detailed walkthrough of the algorithm, see /javascript/mark-and-sweep-algorithm-in-js-full-tutorial.
The Generational Hypothesis
All modern JavaScript engines use generational garbage collection. The key insight is that most objects die young. A temporary string created for logging, an intermediate array built during a calculation, a short-lived event object: these are allocated and discarded quickly.
Objects that survive for a while tend to survive much longer. A global configuration object, a long-lived DOM element, or a module-scoped cache may live for the entire lifetime of the page.
Generational collection splits the heap into a young generation (nursery) and an old generation. The nursery is small and collected frequently using a fast copying collector. Objects that survive a few nursery collections are promoted to the old generation, which is collected less frequently.
Minor GC: Collecting the Nursery
The nursery is collected using a copying collector. The space is split into two halves: the active half where new objects are allocated, and the inactive half used during collection.
When the active half fills up, the collector scans all roots and finds which nursery objects are reachable. It copies each live object to the inactive half, compacting them together. Dead objects are simply abandoned.
When copying completes, the halves swap roles.
This is fast because most nursery objects are dead. The collector only spends time on live objects. If the nursery is 16MB and only 2MB of objects are live, copying those 2MB is quick.
The collector also updates all pointers to point to the new locations.
Major GC: Collecting the Old Generation
The old generation is collected less frequently and uses a different algorithm. Mark-and-sweep is the standard approach.
The mark phase starts from roots and traverses every reachable object, setting a mark bit on each one. The sweep phase walks the entire old generation linearly. Every unmarked object is added to a free list for future allocations.
Some collectors add a compact phase after sweeping. Live objects are moved together to eliminate fragmentation. This is more expensive but prevents the heap from becoming a checkerboard of small free chunks.
Major GC pauses can be significant because the old generation is large. Engines use concurrent marking (running the mark phase in a background thread) and incremental marking (splitting the mark phase into small steps) to keep pauses short.
What Allocates Memory
Every value in JavaScript consumes memory. Some allocations are obvious. Others are easy to overlook.
Objects, arrays, and functions are heap-allocated. Each new object literal, array literal, or function expression allocates memory. Closures allocate memory for the captured variables.
Classes allocate memory for the prototype chain.
Strings are immutable in JavaScript, so every string concatenation creates a new string. Template literals with embedded expressions create new strings. Regular expressions create RegExp objects.
Boxed primitives like new Number(42) allocate wrapper objects, though these are rarely used in practice. Symbol values are allocated once and shared.
How to Write GC-Friendly Code
You cannot control the garbage collector, but you can write code that minimizes its workload.
Avoid unnecessary allocations in hot code paths. A loop that runs thousands of times should not create a new object on every iteration. Reuse objects or use primitive values instead.
Be mindful of closures that capture large objects. A closure that captures a multi-megabyte array keeps that array alive as long as the closure exists.
Clean up event listeners, timers, and subscriptions when they are no longer needed. An active interval that references a large data structure prevents that data from ever being collected.
For practical memory leak diagnosis, see /javascript/fixing-javascript-memory-leaks-complete-guide.
Rune AI
Key Insights
- JavaScript uses automatic garbage collection based on reachability from roots like global objects and the call stack.
- All modern engines use generational collection: a fast nursery for new objects and a slower old generation for survivors.
- The mark-and-sweep algorithm traces live objects from roots and frees everything else.
- GC pauses are short for minor collections and minimized through concurrent techniques for major collections.
- Minimizing allocations in hot paths and cleaning up references reduces GC pressure.
Frequently Asked Questions
Can I force garbage collection in JavaScript?
Does garbage collection pause my application?
Conclusion
Garbage collection is one of JavaScript's greatest conveniences and greatest sources of invisible performance costs. Understanding how reachability, generational collection, and mark-and-sweep work helps you write code that cooperates with the GC rather than fighting it. Minimize allocations in hot paths, clean up references, and let the collector handle the rest.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.