Memory Leaks in Frontend Apps: Detection, Detached DOM, Lingering Listeners
Memory leaks occur when objects that are no longer needed remain strongly referenced, preventing the garbage collector from reclaiming them.
In frontend apps, leaks often hide behind seemingly harmless patterns:
- listeners that never get removed
- intervals that outlive components
- modals repeatedly mounted without proper cleanup
- third-party widgets left alive after navigation
The key mental model is:
- temporary memory growth is normal
- a leak is memory that should have become unreachable, but did not
Strong debugging combines:
- reproducing repeated user flows
- observing memory trends
- taking heap snapshots
- identifying retained objects and detached DOM nodes
Quick Decision Guide
Senior-Level Decision Guide:
- A leak is almost always a reference lifecycle problem, not just high memory usage. - Objects leak when something still references them after their intended lifecycle. - Clean up listeners, timers, observers, WebSockets, subscriptions, and third-party instances. - Detached DOM nodes are one of the most common browser leak patterns. - Use heap snapshot comparison and retaining path analysis to confirm leaks.
Interview framing: Strong answers explain retained references, detached DOM, and a repeatable DevTools debugging workflow.
Garbage Collection Mental Model
Modern browsers use garbage collection to reclaim memory when objects become unreachable.
An object becomes collectible when no references remain pointing to it.
Simplified mental model
JS Objects
│
â–¼
References between objects
│
â–¼
Root references
(window, closures, event listeners, globals)If a path exists from a root reference to an object, the object remains alive.
Example
window
└── event listener
└── closure
└── DOM node
└── subtreeEven if the DOM node was removed from the page, the listener closure may still reference it.
Result: the node and its subtree stay in memory.
Key insight
Garbage collectors remove unreachable objects, not objects that are simply "unused".
What Counts as a Memory Leak
Not every memory spike is a leak.
Examples of normal memory growth:
A real leak usually means:
Example pattern
User opens modal
memory increases
User closes modal
memory should drop
If memory never drops → possible leakSenior engineers focus on retained memory, not just allocation spikes.
Common Frontend Leak Patterns
Frequent causes include:
These patterns often survive component unmounts or route transitions.
Detached DOM: The Classic Browser Leak
Detached DOM
Detached DOM nodes occur when elements are removed from the document but still referenced by JavaScript.
Example scenario:
DOM tree
│
Remove element from page
│
But JS variable still references itconst el = document.getElementById('modal')
modal.remove()
// el still points to removed DOM nodeIf other structures reference that node, the whole subtree remains alive.
Typical causes
Interview framing
"Removed from DOM" does not mean "eligible for garbage collection".
Timers, Listeners, and Observers
These APIs commonly cause leaks when cleanup is forgotten.
Event listeners
window.addEventListener('resize', handler)If handler references component state and never gets removed, the component may remain retained.
Timers
setInterval(update, 1000)Intervals can keep closures alive indefinitely.
Observers
Modern APIs also require cleanup:
IntersectionObserverMutationObserverResizeObserverobserver.disconnect()Other common leak sources
Framework-Specific Leak Patterns
In React or similar frameworks, leaks often come from lifecycle misuse.
Common patterns include:
window or documentReact cleanup example
useEffect(() => {
window.addEventListener('resize', handler)
return () => {
window.removeEventListener('resize', handler)
}
}, [])Lifecycle-based cleanup is one of the most important leak prevention techniques.
DevTools Debugging Workflow
Debugging Memory Leaks with DevTools
A repeatable debugging workflow is essential.
Step 1: Reproduce a repeated flow
Examples:
Step 2: Observe memory trend
Watch whether memory continues growing after garbage collection opportunities.
Step 3: Capture heap snapshots
Compare snapshots before and after repeated flows.
Step 4: Identify retained objects
Look for:
Step 5: Inspect retaining paths
DevTools can show what is keeping an object alive.
Example retaining path:
window
→ event listener
→ closure
→ DOM nodeThis is usually the most important clue in solving a leak.
Prevention Strategies
Useful prevention rules:
Testing long user sessions is important because many leaks only appear after repeated interactions.
Interview Scenarios
Scenario 1
An app becomes slower after opening and closing a modal many times.
Possible cause:
Modal listeners or DOM nodes remain referenced.
Scenario 2
Memory grows during infinite scroll.
Possible cause:
List items or cached data are never released.
Scenario 3
Charts degrade performance after route transitions.
Possible cause:
Chart instances were not destroyed.
Scenario 4
Why does DevTools show "Detached DOM nodes"?
Because removed nodes are still referenced by JavaScript objects.
Scenario 5
Why might a timer cause a memory leak?
Because the interval closure retains references to objects that should have been released.