Debounce, Throttle & requestAnimationFrame
Har keypress pe API? Server doob gaya. Har scroll pe handler? JANK! Debounce = rukne pe call. Throttle = har X ms pe call. rAF = screen ke saath sync.
Debounce waits X milliseconds after the last call before executing. If called again within X ms, it restarts the timer. The function only fires once โ after the caller stops.
Use for: Search input (wait until user stops typing), window resize (wait until resizing stops), form auto-save.
Implementation: clearTimeout + setTimeout โ each call clears the previous timer and starts a new one.
// Basic debounce implementation
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer); // Clear previous timer on every call
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// Usage: Search input
const searchInput = document.getElementById("search");
const handleSearch = debounce(async (query) => {
console.log("Searching:", query);
const results = await fetch("/api/search?q=" + query);
// Only fires 300ms after user STOPS typing!
}, 300);
searchInput.addEventListener("input", (e) => handleSearch(e.target.value));
// User types "hello" quickly โ fires ONCE after 300ms, not 5 times!
Debounce with cancel and flush: Return an object with .cancel() to clear the pending timer and .flush() to immediately execute.
function advancedDebounce(fn, delay) {
let timer;
const debounced = function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
debounced.cancel = () => clearTimeout(timer);
debounced.flush = (...args) => { clearTimeout(timer); fn(...args); };
return debounced;
}
Rule: Debounce = "Wait until they STOP doing it, then act once."
Throttle executes at most once every X ms. First call executes immediately, then subsequent calls are ignored until X ms passes.
Use for: Scroll handlers, mouse move tracking, button click spam prevention, API rate limiting.
Implementation: Track an inThrottle flag + setTimeout to reset it after the limit period.
// Basic throttle implementation
function throttle(fn, limit) {
let inThrottle = false;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args); // Execute immediately on first call
inThrottle = true;
setTimeout(() => inThrottle = false, limit); // Reset after limit
}
// Calls during inThrottle are SILENTLY IGNORED
};
}
// Usage: Scroll handler
const handleScroll = throttle(() => {
console.log("Scroll position:", window.scrollY);
updateLazyImages(); // Fires at most every 100ms while scrolling
}, 100);
window.addEventListener("scroll", handleScroll);
// Without throttle: scroll fires 60+ times per second!
// With throttle: fires at most 10 times per second (100ms intervals).
Throttle with trailing edge: If calls were made during the throttle period, execute ONE more time after the period ends with the latest arguments.
function trailingThrottle(fn, limit) {
let inThrottle = false;
let lastArgs = null;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => {
inThrottle = false;
if (lastArgs) { // If there were calls during throttle
fn(...lastArgs); // Fire one more time with latest args
lastArgs = null;
}
}, limit);
} else {
lastArgs = args; // Save latest args for trailing edge
}
};
}
Debounce vs Throttle: Debounce = "wait until STOP". Throttle = "fire at most every X ms WHILE doing it". Search input โ debounce. Scroll handler โ throttle.
requestAnimationFrame (rAF) schedules a callback to run before the next browser repaint โ synced to display refresh (typically 60fps = ~16.6ms per frame).
Why rAF over setTimeout for visual updates: setTimeout has a 4ms minimum delay, is not synced to refresh, and causes visual jank. rAF fires exactly before each paint.
rAF is automatically paused when the tab is hidden โ saves battery and CPU. setTimeout keeps running in background tabs!
// requestAnimationFrame for smooth animations
function animate() {
element.style.transform = "translateX(" + position + "px)";
position += speed;
if (position < target) {
requestAnimationFrame(animate); // Schedule next frame
}
}
requestAnimationFrame(animate); // Start animation
// rAF throttle โ only update visuals once per frame
function rafThrottle(fn) {
let ticking = false;
return function(...args) {
if (!ticking) {
requestAnimationFrame(() => {
fn.apply(this, args);
ticking = false;
});
ticking = true;
}
};
}
// Usage: Smooth scroll handler
const handleScrollRaf = rafThrottle(() => {
// This runs at most once per frame (60fps)
updateParallax(); // Visual update synced to refresh!
});
window.addEventListener("scroll", handleScrollRaf);
// Cancel animation frame
const animationId = requestAnimationFrame(() => {
drawCanvas();
});
cancelAnimationFrame(animationId); // Cancel if needed
Not every problem needs the same tool. Here is your decision matrix:
// The right tool for the right job
// โ
SEARCH INPUT: Debounce (wait for user to stop typing)
searchInput.addEventListener("input", debounce((e) => {
searchAPI(e.target.value); // One call after user pauses
}, 300));
// โ
SCROLL HANDLER: Throttle or rAF (fire while scrolling)
window.addEventListener("scroll", throttle(() => {
updateLazyImages(); // Regular updates during scroll
}, 100));
// โ
ANIMATION: requestAnimationFrame (sync to refresh)
function animateElement() {
element.style.transform = "translateX(" + x + "px)";
if (x < 500) requestAnimationFrame(animateElement);
}
// โ
RESIZE HANDLER: Debounce (wait until resizing stops)
window.addEventListener("resize", debounce(() => {
recalculateLayout(); // One recalculation after resize ends
}, 250));
// โ
BUTTON SPAM: Throttle (prevent rapid clicks)
buyButton.addEventListener("click", throttle(() => {
processPayment(); // At most once per second
}, 1000));
// โ WRONG: Debounce on scroll โ handler never fires during scroll!
// โ WRONG: Throttle on search โ fires multiple API calls while typing!
Common mistake: Using debounce for scroll โ the user keeps scrolling, so the handler NEVER fires until they stop! Images never load, parallax never updates.
Common mistake: Using throttle for search โ it fires multiple unnecessary API calls while the user is still typing, wasting bandwidth and creating race conditions.
Leading edge debounce: Execute immediately on first call, then debounce subsequent calls. Good for buttons where you want instant feedback.
// Leading edge debounce (instant + then wait)
function leadingDebounce(fn, delay) {
let timer;
let called = false;
return function(...args) {
if (!called) {
fn.apply(this, args); // Execute immediately on first call!
called = true;
}
clearTimeout(timer);
timer = setTimeout(() => called = false, delay);
};
}
Cleanup pattern for SPAs: Always cancel pending timers and remove event listeners when components unmount โ prevents memory leaks and stale updates.
class ScrollHandler {
constructor() {
this.handleScroll = throttle(this.onScroll.bind(this), 100);
window.addEventListener("scroll", this.handleScroll);
}
onScroll() {
updateLazyImages();
}
destroy() {
window.removeEventListener("scroll", this.handleScroll);
// If using debounce with .cancel():
// this.handleScroll.cancel();
}
}
Debounce returning a Promise: Return a Promise that resolves when the debounced function finally executes โ useful for async workflows.
function debouncePromise(fn, delay) {
let timer;
let pendingResolve;
return function(...args) {
clearTimeout(timer);
return new Promise(resolve => {
pendingResolve = resolve;
timer = setTimeout(() => {
const result = fn.apply(this, args);
pendingResolve(result);
}, delay);
});
};
}
The this context: Debounce and throttle wrappers must preserve this โ use function() not arrow functions for the wrapper, so fn.apply(this, args) works correctly.
Lo kar liya โ Key Points:
- โ Debounce waits until the user STOPS calling before executing โ use for search input, auto-save, resize
- โ Throttle executes at most once every X ms โ use for scroll, mouse move, click spam prevention
- โ requestAnimationFrame syncs callbacks to the display refresh rate (60fps) โ use for animations and visual updates
- โ rAF is automatically paused when the tab is hidden, saving CPU and battery
- โ Use debounce for search (wait for pause), throttle for scroll (fire regularly), rAF for animations (sync to screen)
- โ Debounce on scroll is wrong โ handler never fires during scroll. Throttle on search is wrong โ fires too many API calls
- โ Always cancel timers and remove event listeners when components unmount to prevent memory leaks
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login