
Memory Leaks
- 1 installs
- 73.4k repo stars
- Updated June 18, 2026
- thedaviddias/frontendchecklist
Reviews client code for common memory leaks like un-removed listeners and uncleared timers that degrade long-running SPAs.
About
A frontend-checklist JavaScript rule for preventing common memory leak patterns. A developer uses it when SPA memory grows across navigations and eventually crashes the tab.
- Remove listeners and clear timers on component destroy
- Use WeakMap/WeakSet and AbortController to cancel stale work
Memory Leaks by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,912 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedaviddias/frontendchecklist --skill memory-leaksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 73.4k |
| Last updated | June 18, 2026 |
| Repository | thedaviddias/frontendchecklist ↗ |
What it does
Reviews client code for common memory leaks like un-removed listeners and uncleared timers that degrade long-running SPAs.
Files
Prevent common memory leak patterns
Memory leaks cause applications to consume increasing amounts of memory over time, eventually slowing the browser tab or crashing it. In single-page applications, where users don't reload the page, small leaks in components accumulate with each navigation and can make an app unusable after 30 minutes of use.
Quick Reference
- Remove event listeners when the element or component is destroyed
- Clear setInterval and setTimeout when they're no longer needed
- Avoid storing DOM references in long-lived objects after elements are removed
- Use WeakMap and WeakSet when storing metadata about objects you don't own
- Use AbortController to cancel stale async requests when UI state changes
Check
Analyze this code for memory leak patterns: uncleaned event listeners, retained DOM references after removal, closures holding large objects, and uncleared timers. Also flag fetches and async tasks that continue after the owning component or view is gone.
Fix
Fix the memory leaks in this code by cleaning up event listeners, clearing timers, canceling stale async requests, and releasing DOM references when they're no longer needed.
Explain
Explain the four most common JavaScript memory leak patterns and how to detect them with Chrome DevTools.
Code Review
Review scripts, client components, and browser execution paths related to Prevent common memory leak patterns. Flag exact imports, event handlers, runtime side effects, or blocking operations that violate the rule, and state how the change should be verified in the browser.
---
For full implementation details, code examples, and framework-specific guidance, see references/rule.md.
Rule page: https://frontendchecklist.io/en/rules/javascript/memory-leaks
Prevent common memory leak patterns
Identify and avoid the most common JavaScript memory leak sources: forgotten event listeners, retained DOM references, closures holding large objects, and uncleared timers.
Priority: high · Difficulty: intermediate · Time: 25 min
--- JavaScript's garbage collector frees memory for objects with no remaining references. Memory leaks occur when your code holds references to objects that are no longer needed but can't be collected.
Code Example
// ❌ Leaks: listener is never removed, holds a reference to the element
class SearchComponent {
constructor() {
this.input = document.getElementById('search')
document.addEventListener('keydown', this.handleKeydown)
// If SearchComponent is garbage collected, the listener remains
}
handleKeydown = (event) => {
if (event.key === 'Escape') this.clear()
}
}
// ✅ Good: always provide a cleanup path
class SearchComponent {
constructor() {
this.input = document.getElementById('search')
this.handleKeydown = this.handleKeydown.bind(this)
document.addEventListener('keydown', this.handleKeydown)
}
destroy() {
document.removeEventListener('keydown', this.handleKeydown)
this.input = null
}
}Why It Matters
Memory leaks cause applications to consume increasing amounts of memory over time, eventually slowing the browser tab or crashing it. In single-page applications, where users don't reload the page, small leaks in components accumulate with each navigation and can make an app unusable after 30 minutes of use.
Event Listener Cleanup
Event listeners are one of the most common leak sources because they keep both the handler and the referenced objects alive.
// ❌ Anonymous listener cannot be removed later
button.addEventListener('click', () => doSomething())
// ✅ Keep a stable reference
const handleClick = () => doSomething()
button.addEventListener('click', handleClick)
function cleanup() {
button.removeEventListener('click', handleClick)
}// ✅ AbortController removes a group of listeners at once
const controller = new AbortController()
window.addEventListener('scroll', onScroll, { signal: controller.signal })
window.addEventListener('resize', onResize, { signal: controller.signal })
function cleanup() {
controller.abort()
}Uncleared Timers
// ❌ Leaks: interval fires forever even after the component is gone
function startPolling(callback) {
setInterval(callback, 5000)
}
// ✅ Good: store the ID and clear it on cleanup
function startPolling(callback) {
const intervalId = setInterval(callback, 5000)
return () => clearInterval(intervalId) // return cleanup function
}
// Usage
const stopPolling = startPolling(syncData)
// Later, when done:
stopPolling()Cancel Stale Async Requests
Long-running fetches can hold closures, state setters, and response objects alive after a component has already unmounted or the user has moved on:
useEffect(() => {
const controller = new AbortController()
async function loadSearchResults() {
const response = await fetch(`/api/search?q=${query}`, {
signal: controller.signal,
})
const data = await response.json()
setResults(data)
}
loadSearchResults().catch((error) => {
if (error.name !== 'AbortError') {
throw error
}
})
return () => controller.abort()
}, [query])Retained DOM References
// ❌ Leaks: detached DOM tree held in memory through a JS object
const cache = {}
function cachePanel(id) {
cache[id] = document.getElementById(id)
document.getElementById(id).remove() // removed from DOM but still in cache
}
// ✅ Good: use WeakMap so DOM nodes can be garbage collected
const cache = new WeakMap()
function cachePanel(element) {
cache.set(element, { timestamp: Date.now() })
element.remove() // WeakMap won't prevent GC
}Closures Holding Large Data
// ❌ Leaks: the event listener closure holds a reference to largeData
function processLargeData() {
const largeData = new Array(1_000_000).fill('x')
const result = computeSummary(largeData)
button.addEventListener('click', () => {
// Only result is used, but largeData is captured in the closure
displayResult(result)
})
}
// ✅ Good: don't capture what you don't need
function processLargeData() {
const largeData = new Array(1_000_000).fill('x')
const result = computeSummary(largeData)
// largeData goes out of scope and can be collected
button.addEventListener('click', () => {
displayResult(result) // Only result is captured
})
}React Component Cleanup
// ✅ Clean up subscriptions and timers in useEffect cleanup
useEffect(() => {
const subscription = store.subscribe(handleChange)
const timerId = setInterval(pollForUpdates, 10000)
return () => {
subscription.unsubscribe()
clearInterval(timerId)
}
}, [])Framework-specific cleanup still belongs to the same rule:
<script setup>
function handleScroll() {}
onMounted(() => window.addEventListener('scroll', handleScroll))
onUnmounted(() => window.removeEventListener('scroll', handleScroll))
</script>Standards
- Use MDN: JavaScript Guide as the standard for how this JavaScript pattern should behave in production, not just in a small local example.
- Use web.dev: Learn JavaScript as the standard for how this JavaScript pattern should behave in production, not just in a small local example.
Verification
1. Verify the behavior in the browser after the code change, not only in static analysis. 2. Inspect DevTools Network or Performance panels when the rule affects loading or execution order. 3. Test the primary user flow and one edge case triggered by the changed script path. 4. Confirm the code still behaves correctly when the feature is delayed, lazy-loaded, or fails. 5. Navigate away or change inputs mid-request and confirm stale fetches are aborted rather than updating dead UI state.