Throttling and Debouncing: The Superheroes of Event Handling
In the world of JavaScript event handling, debouncing and throttling are like superheroes that swoop in to save the day. These techniques allow us to control how many times a function gets called when an event fires repeatedly. Without them, our apps would be sluggish, make too many API calls, and overload users with updates.
But like any superhero duo, throttling and debouncing have their own distinct styles and use cases. Understanding how they complement each other is key to utilizing them effectively in your code.
Debouncing vs. Throttling
Why you should care?
Here are some reasons why mastering throttling and debouncing should be on every JavaScript developer’s bucket list:
Improve app performance by eliminating unnecessary computations and API calls
Create smoother UIs by controlling update frequency
Reduce server load by limiting requests
Optimize for user experience over machine optimization
Avoid rate limits and extra costs from over-calling APIs
Help tame hard-to-reproduce concurrency bugs
Let’s break down how these two techniques work, when to use each one, and how you can wield them in your apps.
Understanding the Basics
What is Debouncing?
Debouncing enforces that a function will not be called again until a certain amount of time has passed without it being called. This ensures that the function is only invoked after the input/event is completed or has stopped for a given duration.
“Wait a sec! Debouncing in Action”
For example, debouncing a button click handler by 250ms would mean that no matter how many times the button is clicked within that timeframe, the handler will only be invoked once 250ms after the last click.
What is Throttling?
Throttling limits the rate at which a function can be called over time. This is done by allowing a function to execute at most once during a given window of time.
“The Speed Limit! Throttling in Action”
For example, throttling a button click handler by 250ms would mean the handler can only be invoked once every 250ms, no matter how many actual clicks occur during that time.
Diving Deeper into Debouncing
How Debouncing Works
Under the hood, a debounced function contains a timer that tracks elapsed time between function calls. Each time the debounced function is invoked, the timer is cleared and reset.
This means that as long as the wrapped function keeps getting called within the specified time interval, the underlying function will not be executed.
Only when enough time has elapsed without any further calls, will the underlying function finally be invoked.
The Art of Holding Back: Delaying Function Invocation
Here is some pseudocode to illustrate the debounce logic:
let timeout;
function debounce(func, delay) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func();
}, delay);
}
By resetting the timer each time, we ensure func() won’t run until after input has stopped for delay milliseconds.
Real World Examples of Debouncing
Here are some common use cases where debouncing shines:
Search boxes — debounce API calls triggered on each keystroke
Window resize handlers — debounce to avoid resize thrashing
Scroll handlers — debounce to prevent scroll overload
Button clicks — debounce to prevent accidental double submits
Mouse movement — debounce for smoother painting/drawing
Debouncing code example in React.js
Here is an example of debouncing a search API call in React:
import { useState, useCallback } from 'react';
function Search() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearch = useCallback(
debounce((term) => {
// Call API with the latest search term after 500ms of inactivity
doSearch(term);
}, 500),
[]
);
function doSearch(term) {
// Perform API call with the latest search term
// This function will be called after a 500ms inactivity period
}
function handleChange(e) {
const { value } = e.target;
setSearchTerm(value);
debouncedSearch(value); // Debounced search API call
}
return (
<div>
<input
type="text"
value={searchTerm}
onChange={handleChange}
placeholder="Search..."
/>
</div>
);
}
Less is More: Reducing API Calls with Debouncing
Avoids needlessly calling APIs on every input event
Saves server resources by limiting requests
Prevents hitting rate limits on APIs
Creates smoother UIs by eliminating premature updates
Ensures function is only called when input is truly finished
Diving Deeper into Throttling
How Throttling Works
Under the hood, throttling also uses a timer to track elapsed time. But unlike debouncing, it ensures the wrapped function is called at regular intervals as long as input continues.
Each time the throttled function is called, it checks if enough time has passed since the last invocation. If yes, it makes the call, otherwise it skips execution.
Slow and Steady: Limiting Function Invocation
Here is some pseudocode showing the throttle logic:
let timeout;
function throttle(func, limit) {
if (!timeout) {
func();
timeout = setTimeout(() => {
timeout = null;
}, limit);
}
}
This guarantees func() is called at most once every limit milliseconds.
Real World Examples of Throttling
Here are some common throttling use cases:
Scroll handlers — throttle to improve performance
Resize handlers — throttle to avoid resize thrashing
Save buttons — throttle to prevent duplicate submissions
Live search boxes — throttle to limit network requests
Game loops — throttle to cap frame rates
Throttling code example in React.js
Here is an example of throttling a scroll handler in React:
import { useCallback, useRef, useEffect } from 'react';
function ScrollTracker() {
const prevScrollY = useRef(0);
// Throttle the handleScroll function to execute at most once every 100ms
const throttledScroll = useCallback(
throttle(handleScroll, 100),
[],
);
function handleScroll() {
const currentScrollY = window.scrollY;
if (currentScrollY < prevScrollY.current) {
console.log('scrolling up');
} else {
console.log('scrolling down');
}
prevScrollY.current = currentScrollY;
}
useEffect(() => {
// Add the throttledScroll function as the event listener for the scroll event
window.addEventListener('scroll', throttledScroll);
return () => {
// Clean up by removing the event listener when the component is unmounted
window.removeEventListener('scroll', throttledScroll);
};
}, [throttledScroll]);
return null;
export default ScrollTracker;
Here we throttle the scroll handler to cap how often it runs during continuous scrolling.
Keeping it Smooth: Ensuring Consistent Performance with Throttling
Prevents UI overload by limiting update frequency
Avoids janky/stuttering animations and scrolling
Improves consistency of framerates in games
Ratelimits network requests during continuous events
Ensures function is called at regular controlled intervals
The Great Divide — Debouncing vs. Throttling
Now that you have an intuition for how debouncing and throttling work, let’s highlight the key differences:
Criteria | Debouncing | Throttling |
Function Invocation | Delays invoking function until input pauses | Invokes function at most once over a time period |
Use Cases | Reducing high-frequency expensive operations | Rate-limiting UI updates and preventing duplicates |
Execution Requirement | Waits for a pause in input to execute | Calls the function at regular intervals |
Input Handling | Captures the last input during the pause | Captures and processes every input during the period |
Time Granularity | Suitable for longer pause periods | Suitable for shorter time intervals |
Event Triggering | Usually triggered on input release | Usually triggered on input press or release |
When to use which? Making the Right Choice
Here are some rules of thumb on when to debounce vs throttle:
Use debouncing for expensive operations like API calls to avoid overloading.
Use throttling to limit UI update frequency and maintain visual consistency.
Debounce one-off events like button clicks.
Throttle repetitive events like window resizing and scrolling.
Debounce when you only care about the final state after input has stopped.
Throttle when you want intermediate updates at a controlled rate.
Conclusion
Debouncing and throttling are invaluable techniques for optimizing application performance. While their implementations differ, debouncing and throttling serve complementary purposes. Debouncing reduces noise from intermittent events, while throttling smooths out rapid-fire triggers.
Understanding when to reach for which technique takes some experience. But by mastering both, you can build apps that are efficient, responsive, and delightful.
So next time your users are furiously tapping and scrolling, don’t panic. Just take a deep breath, and reach for debounce and throttle — your new superhero BFFs.
I hope you found this comprehensive guide on Debouncing and Throttling helpful. If you enjoyed reading it, consider giving it a clap and following me for more content like this. You can also connect with me on LinkedIn and check out my other projects on GitHub.
Stay tuned for more in-depth guides and tutorials!