Memory Management & Garbage Collection
Memory Is Not Infinite
JavaScript manages memory for you — you never call malloc or free. But "automatic" does not mean "free". The garbage collector can only reclaim memory it can prove is unreachable. If you accidentally keep a reference to something you no longer need, it stays in memory forever. In a long-running application — a Node.js server, a single-page app that runs for hours — this is how you run out of memory and crash.
Start by understanding what reachability means:
The GC starts from roots — global variables, the current call stack, and active closures — and follows every reference. Anything reachable from a root stays. Anything not reachable is collected. Circular references between objects are not a problem as long as the cycle itself is unreachable from a root.
The Generational Heap
V8's heap is split into two main regions based on the observation that most objects die young — allocated in a function, used briefly, then immediately eligible for collection:
New Space (Young Generation) ~1–8 MB
├── From-space
└── To-space ← Minor GC (Scavenge) runs here, very fast
Old Space (Old Generation) ~hundreds of MB
└── Objects that survived 2+ minor GCs live here
Major GC (Mark-Sweep-Compact) runs here, slower
Short-lived allocations are cheap — the minor GC runs in microseconds and reclaims them. The old generation is collected less often but each collection takes longer. You want important persistent data in old space, and transient computations to stay short-lived.
Memory Leak Pattern 1 — Forgotten Event Listeners
The most common leak in browser applications. An event listener holds a reference to its callback, which closes over variables, which keeps entire object graphs alive:
The pattern: always pair addEventListener with removeEventListener, and always return a cleanup function from anything that registers a listener. React's useEffect return value exists precisely for this reason.
Memory Leak Pattern 2 — Unbounded Caches
A cache with no eviction policy is a memory leak with a delay:
An LRU cache bounded to 100 entries uses constant memory regardless of how many unique keys are requested. This is a production necessity for any cache keyed by user input or dynamic data.
Memory Leak Pattern 3 — Closures Holding Large Scopes
A closure captures its entire surrounding scope — not just the variables it uses. This can unexpectedly keep large objects alive:
Memory Leak Pattern 4 — Detached DOM Nodes
This one is browser-specific but worth understanding. If you keep a JavaScript reference to a DOM node after it has been removed from the document, the entire subtree stays in memory even though the user cannot see it:
Measuring Memory — What You Can Observe
In the sandbox performance.memory is not available, but in a real browser or Node.js process you have tools:
WeakMap and WeakRef as Memory-Safe Patterns
You saw these in the previous lesson. The memory angle: WeakMap entries do not prevent GC, making them the right tool for any cache or metadata store keyed by objects whose lifetime you do not control:
What You Have Learned
Memory management in JavaScript is automatic but not effortless. The garbage collector handles reachability correctly — your job is to not accidentally keep things reachable longer than necessary.
The key ideas:
- The GC collects objects that are unreachable from roots — circular references between unreachable objects are handled correctly
- V8's heap is generational: most objects die young in New Space (cheap minor GC); survivors are promoted to Old Space (less frequent, slower major GC)
- The four common leak patterns: forgotten event listeners, unbounded caches, closures holding large scopes, and detached DOM node references
- An LRU cache with a fixed size bound is the correct pattern for any cache keyed by dynamic data
- WeakMap is the only cache that cannot leak — entries disappear when their key object is collected
- Measure with
process.memoryUsage()in Node.js, Chrome DevTools Memory tab in the browser, or a manual allocation tracker during development - Prefer extracting what you need from large data before closing over it — do not rely on the engine being smart about unused variables in scope
In the next lesson: Functional Programming Patterns — pure functions, immutability, function composition, currying, and monads. The programming paradigm that eliminates an entire category of bugs by making state changes impossible rather than just careful.