V8 #2 — Memory Leaks & Garbage Collection

Mark-and-sweep, generational GC, the four leak patterns (forgotten timers, detached DOM, closure captures, unbounded caches), and heap snapshot debugging.

12 min read
JavaScript
Memory
GC
Performance

TABLE OF CONTENTS
V8 #2 — Memory Leaks & Garbage Collection

JavaScript's garbage collector automatically frees memory, but certain patterns prevent it from recognizing memory as collectible. This article covers how mark-and-sweep works, the four classic leak patterns, and how to debug memory issues.

Prerequisites: V8 #1 — JIT Compilation, JS Foundations #3 — Closures


1. Mark-and-Sweep — How GC Works

The garbage collector periodically finds objects that are no longer reachable from the root (global object, call stack):

Loading editor...

Generational GC: V8 splits the heap into new space (young, short-lived objects) and old space (survived multiple GC cycles). Most objects die young — the scavenger (minor GC) handles them cheaply.


2. Leak Pattern #1 — Forgotten Timers and Intervals

The most common SPA leak — intervals that keep running after a component unmounts:

Loading editor...


3. Leak Pattern #2 — Detached DOM Nodes

A JavaScript reference to a removed DOM element keeps the element in memory:

Loading editor...


4. Leak Pattern #3 — Closure Capturing Large Data

Closures capture the entire scope, not just the variables they use:

Loading editor...


5. Leak Pattern #4 — Unbounded Caches

Caches that grow without limit eventually exhaust memory:

Loading editor...


6. Detecting Leaks — The Heap Snapshot Pattern

In Chrome DevTools, the Memory panel offers heap snapshots:

Loading editor...


Key Takeaways

Leak PatternCauseFix
Forgotten timerssetInterval without clearIntervalCleanup in useEffect return / componentWillUnmount
Detached DOMJS reference to removed elementNull out references when DOM elements are removed
Closure capturesInner function captures entire outer scopeExtract only needed values before closing over
Unbounded cachesMap/Object that only ever growsUse LRU cache with max size; TTL-based eviction
  • GC is nondeterministic — you can't force it (window.gc() only in dev mode with --expose-gc).
  • Use WeakMap/WeakSet for object-associated data that shouldn't prevent GC.
  • Heap snapshots are the primary debugging tool — learn to compare before/after.

Next: Engine #1 — Hoisting & TDZ Internals — what the engine actually does during parsing and execution.


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI