Performance & Memoryhigh

Memory Leaks in JavaScript

A memory leak occurs when objects that are no longer needed remain reachable (directly or through closures/references), preventing garbage collection. Common sources: forgotten event listeners, closures over large data, detached DOM nodes.

Memory anchor

Memory leaks are like forgetting to turn off faucets in a house — event listeners, closures, and detached DOM nodes are dripping water. Individually tiny, but leave enough running and the house floods.

Expected depth

The four main leak patterns: (1) Forgotten event listeners — addEventListener without a corresponding removeEventListener on teardown; (2) Closures holding large objects — a short-lived closure references a large array/object, keeping it alive as long as any function in the closure chain is alive; (3) Global variables — accidentally assigning to an undeclared variable in non-strict mode creates a global; (4) Detached DOM nodes — removing a node from the DOM while JS still holds a reference to it keeps the entire subtree in memory.

Deep — senior internals

V8's garbage collector uses a generational, mark-and-sweep algorithm. Objects in 'young generation' (nursery) are collected frequently; surviving objects are promoted to 'old generation' (tenured), collected less often. A leak keeps an object in old generation indefinitely. Detecting leaks: Chrome DevTools heap snapshots — compare snapshots before and after an action; look for detached DOM trees, growing counts of specific object types. The 'three snapshot technique': take snap after initial load, trigger the suspected leak action N times, take snap — objects that appear N times are leak candidates. WeakRef and FinalizationRegistry can help in frameworks that must track objects without owning them.

🎤Interview-ready answer

The most common JavaScript memory leaks are: event listeners not removed on component unmount, closures that inadvertently capture and hold large objects alive, and DOM references held in JavaScript while the node is removed from the tree (detached DOM subtrees). In SPAs, module-level or closure-scoped data that grows over time is a major source. Detect with Chrome's Memory panel heap snapshots; fix by explicitly removing listeners, nullifying references, and using WeakMap for object metadata.

Common trap

Using an arrow function as an event listener makes it impossible to remove: element.addEventListener('click', () => handler()) — the arrow function is a new object each time, so removeEventListener with 'the same' arrow function won't match. Always store a reference to the listener function to remove it later.

Related concepts