Search inputs lagging? Scroll events killing performance? Understanding debounce vs throttle is critical for responsive UIs.
Debounce – Wait Until User Stops:
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Usage: Search input
const searchInput = document.getElementById('search');
const performSearch = debounce((query) => {
console.log('Searching for:', query);
// Actual API call here
fetch(`/api/search?q=${query}`);
}, 500);
searchInput.addEventListener('input', (e) => {
performSearch(e.target.value);
});
How Debounce Works:
User types “javascript”:
• j → timer starts (500ms)
• ja → timer resets (500ms from now)
• jav → timer resets again
• java → timer resets
• javas → timer resets
• javasc → timer resets
• javascr → timer resets
• javascri → timer resets
• javascrip → timer resets
• javascript → timer resets
• (user stops typing)
• 500ms passes → API call executes ONCE
Result: 1 API call instead of 10. 90% reduction in server load.
Throttle – Execute at Regular Intervals:
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
}, limit);
}
};
}
// Usage: Infinite scroll
const handleScroll = throttle(() => {
const scrollPosition = window.scrollY + window.innerHeight;
const pageHeight = document.documentElement.scrollHeight;
if (scrollPosition >= pageHeight - 100) {
console.log('Load more items');
loadMoreItems();
}
}, 200);
window.addEventListener('scroll', handleScroll);
How Throttle Works:
User scrolls continuously for 1 second:
• 0ms: Function executes
• 50ms: Blocked (within 200ms limit)
• 100ms: Blocked
• 150ms: Blocked
• 200ms: Function executes
• 250ms: Blocked
• 300ms: Blocked
• 400ms: Function executes
• 600ms: Function executes
• 800ms: Function executes
• 1000ms: Function executes
Result: 6 executions instead of 60+ (one per scroll event). Still responsive, but 90% less work.
Decision Tree – Which to Use:
// Use DEBOUNCE for:
- Search inputs (wait until user finishes typing)
- Form validation (validate after user stops typing)
- Window resize handling (wait until resize complete)
- Auto-save draft (save after user stops editing)
// Use THROTTLE for:
- Scroll events (infinite scroll, parallax effects)
- Mouse move tracking (drawing, games)
- Button clicks (prevent double-submit)
- API rate limiting (max X requests per second)
Real Performance Impact:
// Without debounce/throttle
searchInput.addEventListener('input', (e) => {
fetch(`/api/search?q=${e.target.value}`); // Fires on EVERY keystroke
});
// Typing "javascript" = 10 keystrokes = 10 API calls
// At 200ms per request = 2 seconds of unnecessary load
// Multiply by 1000 concurrent users = server meltdown
// With debounce (500ms)
// Typing "javascript" = 1 API call
// 0.2 seconds total
// 90% reduction in server load
Pro Tip – Leading Edge Throttle:
Sometimes you want immediate execution, then throttling:
function throttleLeading(func, limit) {
let lastRun = 0;
return function(...args) {
const now = Date.now();
if (now - lastRun >= limit) {
func.apply(this, args);
lastRun = now;
}
};
}
// Button click: Execute immediately, then block for 2 seconds
const submitButton = document.getElementById('submit');
submitButton.addEventListener('click', throttleLeading(() => {
console.log('Form submitted!');
submitForm();
}, 2000));
First click executes instantly (good UX). Subsequent clicks within 2 seconds are ignored (prevent double-submit).
