Fossils🧠 ConceptualCommon JavaScript Memory Leaks
ðŸĢHatchlingJavaScriptPerformanceMemory

Common JavaScript Memory Leaks

Memory leaks in SPAs are silent performance killers. This question tests whether you've debugged real production issues or only worked on greenfield code.

Common JavaScript Memory Leaks

Interview Question: "What are the most common causes of memory leaks in JavaScript? How do you detect them?"

The Senior Answer

"Memory leaks in JavaScript happen when objects that are no longer needed are still referenced, preventing garbage collection. In SPAs, this is a slow kill — the app works fine for the first 10 minutes, then gradually degrades over hours."

The Five Common Patterns

"I see five patterns repeatedly:

1. Forgotten event listeners — Adding listeners in a component that mounts/unmounts without cleanup. Each mount adds a new listener; none are removed.

2. Orphaned closures — A closure captures a large object it doesn't need, and the closure itself lives indefinitely (stored in a cache, attached to a long-lived event).

3. Unbounded caches — Maps or objects used as caches that grow forever without eviction.

4. Detached DOM nodes — Removing a DOM element but keeping a JavaScript reference to it. The entire subtree stays in memory.

5. Forgotten timers — setInterval running after the component or feature that started it has been destroyed."

Prevention Patterns

// React: cleanup in useEffect
useEffect(() => {
  const handler = () => updatePosition();
  window.addEventListener('scroll', handler);
  return () => window.removeEventListener('scroll', handler);
}, []);
 
// AbortController for multiple listeners
useEffect(() => {
  const controller = new AbortController();
  window.addEventListener('resize', onResize, { signal: controller.signal });
  window.addEventListener('scroll', onScroll, { signal: controller.signal });
  return () => controller.abort(); // Removes all at once
}, []);
 
// WeakMap for metadata that shouldn't prevent GC
const metadata = new WeakMap();
function annotate(element, data) {
  metadata.set(element, data);
  // When element is GC'd, metadata entry is automatically removed
}
 
// LRU cache with max size
const cache = new Map();
function cachedFetch(key) {
  if (cache.has(key)) return cache.get(key);
  const result = expensiveFetch(key);
  cache.set(key, result);
  if (cache.size > 100) cache.delete(cache.keys().next().value);
  return result;
}

Detection

"I use the three-snapshot technique in Chrome DevTools:

  1. Take heap snapshot (baseline)
  2. Perform the action (navigate, open/close modal)
  3. Undo the action
  4. Take another snapshot
  5. Compare — objects in snapshot 2 but not in 3 are leaks

The Allocation Timeline shows when memory is allocated over time. Growing without drops = leak. The Performance Monitor shows real-time JS heap size — if it trends upward over minutes, you have a leak."

Red Flags

  • Only mentioning "not using const" or variable leaks (that's not a real memory leak pattern)
  • Not knowing the event listener cleanup pattern
  • Never having used heap snapshots
  • Not mentioning WeakMap/WeakRef as solutions