JavaScript automatically manages memory through garbage collection using a mark-and-sweep algorithm — objects are freed when they become unreachable from root references, but common patterns like forgotten timers and detached DOM nodes cause memory leaks.
The GC starts from root references (global, call stack, closures), marks all reachable objects, and frees everything else. Runs automatically.
Forgotten timers/intervals, detached DOM nodes, unremoved event listeners, closures capturing large scopes, and accidental global variables.
Hold weak references to keys — objects can be garbage collected even while in a WeakMap. Ideal for metadata without preventing collection.
ES2021 tools for weak references and cleanup callbacks after GC — used for caches and resource management, but should be used sparingly.
Chrome Memory tab provides heap snapshots, allocation timelines, and the three-snapshot technique for finding objects that should have been freed.
JavaScript automatically allocates memory when objects are created and frees it when they are no longer needed. Understanding how this works — and what can go wrong — is essential for building performant applications and is a common senior-level interview topic.
Modern JavaScript engines (V8, SpiderMonkey, JavaScriptCore) use the mark-and-sweep algorithm. The garbage collector starts from "root" references — the global object, the current call stack, and active closures — and traverses all reachable objects, marking them as alive. Any object not marked (unreachable from any root) is swept and its memory is freed. This runs periodically and automatically — developers cannot trigger it manually.
The tricky part is step 3 — the GC can only free memory that is truly unreachable. If your code accidentally keeps a reference to an object you no longer need, the GC cannot collect it. This is a memory leak.
Forgotten timers and intervals: setInterval(callback, 1000) keeps the callback (and everything it references via closure) alive until clearInterval is called. If you forget to clear it — especially when a component unmounts — the interval and its references leak.
Detached DOM nodes: Removing a DOM element from the document doesn't free it if JavaScript still holds a reference. Storing DOM elements in variables or arrays and then removing them from the page creates "detached DOM trees" that cannot be garbage collected.
Event listeners not removed: Adding event listeners to elements without removing them (especially in SPAs where components mount/unmount) keeps both the listener function and everything in its closure alive.
Closures capturing large scopes: A closure only needs one variable from its outer scope, but if the engine hasn't optimized away the rest, the entire scope may be retained. Large objects in the same scope as a long-lived closure can leak.
Accidental global variables: Assigning to an undeclared variable in non-strict mode creates a global variable that persists for the lifetime of the page.
WeakMap and WeakSet hold weak references to their keys — if the key object has no other references, it can be garbage collected even though it's in the WeakMap. This makes them ideal for associating metadata with objects (like DOM elements or class instances) without preventing their collection. WeakMap keys must be objects, and you cannot iterate over a WeakMap or check its size.
WeakRef creates a weak reference to any object — deref() returns the object if it's still alive, or undefined if it's been collected. FinalizationRegistry lets you register a callback that runs after an object is garbage collected. Both are advanced tools for cache implementations and resource cleanup, but should be used sparingly.
Chrome DevTools Memory tab provides heap snapshots (compare two snapshots to find growing allocations), allocation timelines (see which objects are allocated over time), and the allocation profiler. The "three snapshot technique" — take a snapshot, perform an action, take another snapshot, undo the action, take a third — reveals objects that should have been freed but weren't.
JavaScript manages memory automatically, but cannot prevent leaks caused by code that accidentally retains references. The five main leak patterns are: forgotten timers, detached DOM nodes, unremoved event listeners, closures over large scopes, and accidental globals. WeakMap/WeakSet solve the metadata-association pattern by allowing GC on their keys.
Fun Fact
JavaScript's garbage collector in V8 is called Orinoco and uses a generational approach — new objects are allocated in a 'young generation' that is collected frequently and quickly, while long-lived objects are promoted to an 'old generation' that is collected less often. Most objects die young, making this highly efficient.