Debounce and throttle event handlers
Use debounce or throttle for high-frequency events like scroll, resize, and input to improve performance.
- Debounce: delays execution until activity stops (search inputs, form validation)
- Throttle: limits execution rate (scroll, resize handlers)
- Use 150-300ms delay for user input, 100ms for scroll/resize
- Clean up event listeners to prevent memory leaks
Rule Details
High-frequency events like scroll, resize, and input can fire hundreds of times per second, causing performance issues if handlers are not rate-limited.
Code Examples
Debounce (wait for pause)
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
clearTimeout(timeout)
timeout = setTimeout(() => func.apply(this, args), wait)
}
}
// Usage: Search input
const searchInput = document.querySelector('#search')
const handleSearch = debounce((e) => {
fetchSearchResults(e.target.value)
}, 300)
searchInput.addEventListener('input', handleSearch)Throttle (limit rate)
function throttle(func, limit) {
let inThrottle
return function executedFunction(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// Usage: Scroll handler
const handleScroll = throttle(() => {
updateScrollProgress()
}, 100)
window.addEventListener('scroll', handleScroll)Why It Matters
High-frequency events fire hundreds of times per second, causing UI jank, excessive API calls, and poor Interaction to Next Paint (INP) scores.
When to Use Each
| Pattern | Use Case | Example |
|---|---|---|
| Debounce | Wait for pause in activity | Search input, form validation |
| Throttle | Limit execution rate | Scroll position, resize handlers |
Framework Examples
React
import { useMemo, useCallback } from 'react'
import { debounce } from 'lodash-es'
function SearchComponent() {
const [query, setQuery] = useState('')
// Memoize debounced function
const debouncedSearch = useMemo(
() => debounce((value) => {
fetchResults(value)
}, 300),
[]
)
// Cleanup on unmount
useEffect(() => {
return () => {
debouncedSearch.cancel()
}
}, [debouncedSearch])
const handleChange = (e) => {
setQuery(e.target.value)
debouncedSearch(e.target.value)
}
return <input value={query} onChange={handleChange} />
}Vue 3
<script setup>
import { ref, onUnmounted } from 'vue'
import { useDebounceFn } from '@vueuse/core'
const query = ref('')
const results = ref([])
const search = useDebounceFn(async (value) => {
results.value = await fetchResults(value)
}, 300)
function handleInput(e) {
query.value = e.target.value
search(e.target.value)
}
</script>
<template>
<input :value="query" @input="handleInput" />
</template>Common Mistakes
// ❌ Bad: Creating new debounced function on every render
function Component() {
const handleInput = debounce((e) => {
search(e.target.value)
}, 300) // New function every render!
}
// ❌ Bad: Not cleaning up debounced calls
useEffect(() => {
// No cleanup for pending debounced calls
}, [])
// ❌ Bad: Debouncing inside the handler
element.addEventListener('scroll', () => {
debounce(updateUI, 100)() // Creates new debounce each time!
})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
- Verify the behavior in the browser after the code change, not only in static analysis.
- Inspect DevTools Network or Performance panels when the rule affects loading or execution order.
- Test the primary user flow and one edge case triggered by the changed script path.
- Confirm the code still behaves correctly when the feature is delayed, lazy-loaded, or fails.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Review this JavaScript code for scroll, resize, input, mousemove, or other high-frequency event handlers that should use debounce or throttle.
Fix
Auto-fix issues
Add debounce or throttle to high-frequency event handlers to limit execution rate and improve performance.
Explain
Learn more
Explain the difference between debounce and throttle, and when to use each pattern for optimal performance.
Review
Code review
Review scripts, client components, and browser execution paths related to Debounce and throttle event handlers. 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.