Memory Leaks — Closures, Detached DOM
debt(d7/e5/b5/t7)
Closest to 'only careful code review or runtime testing' (d7). Chrome DevTools is listed in detection_hints.tools, but it requires manual heap snapshot inspection and pattern recognition — not automated. The code_pattern hint (addEventListener) catches only one common case; other leaks (circular references, cache bloat, detached DOM storage) are silent until memory profiling reveals them.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix mentions AbortController and WeakMap replacements, which are single-concept swaps (e3-worthy), but memory leaks typically infect multiple event handlers, lifecycle hooks, and closure scopes across a component or module. Fixing them comprehensively requires audit and refactor of several interdependent pieces.
Closest to 'persistent productivity tax' (b5). Memory leak patterns (addEventListener without cleanup, closure-captured DOM refs, unbounded caches) are easy to introduce during feature development and accumulate silently. They don't rewrite the architecture (b9), but they slow down long-running SPAs and Node processes, forcing developers to become vigilant about lifecycle cleanup patterns, testing strategies, and monitoring. Every async callback and component unmount becomes a potential pitfall.
Closest to 'serious trap' (t7). The core misconception ('GC makes leaks impossible') directly contradicts how GC actually works and is reinforced by other languages/contexts where manual memory management is explicit. Many developers assume GC is sufficient, leading to the 'obvious' mistake of not cleaning up event listeners or DOM references. Common_mistakes emphasizes the disconnect between expectation (GC handles it) and reality (references must be explicitly cleared).
TL;DR
Explanation
JavaScript uses a mark-and-sweep GC — objects are freed when no longer reachable. Leaks occur when references are accidentally kept: (1) Closures that capture large outer scopes, (2) Event listeners on removed DOM nodes keep the nodes alive, (3) Global variables holding data that grows, (4) setInterval/setTimeout callbacks not cleared, (5) DOM references stored in JS after the element is removed, (6) Large Maps/Sets used as caches with no eviction. Detecting: Chrome DevTools Memory tab, heap snapshots, allocation timelines. Node.js: --inspect + heapdump.
Common Misconception
Why It Matters
Common Mistakes
- Not removing event listeners when components unmount.
- Storing DOM references in closures after elements are removed.
- Using regular Map/Set as a cache without size limits or TTL eviction.
- Circular references between JS objects and DOM nodes.
Code Examples
// Event listener leak:
const button = document.getElementById('btn');
function handleClick() { /* captures large data */ }
button.addEventListener('click', handleClick);
// Later: button removed from DOM but listener keeps it alive
// Remove listeners when done:
const controller = new AbortController();
button.addEventListener('click', handleClick, { signal: controller.signal });
// To remove all listeners:
controller.abort();
// WeakMap for DOM associations — GC-friendly:
const meta = new WeakMap();
meta.set(element, { clicks: 0 }); // element can be GC'd