Both throttle and debounce are used to optimize expensive, frequently executed actions. They're two of the most common performance optimization techniques.
What is debouncing?
Debouncing is a technique used to improve the performance of frequently executed actions, by delaying them, grouping them, and only executing the last call.
Picture this: you want a search input where results are queried automatically as you're typing into it.
You wouldn't want to ping the backend on every keystroke, rather you only care about the final value. That's the perfect use case for a debounce function, in fact, it's a very common one.
Use the debounce function when you only care about the final result of the expensive action.
Real quick - would you like to master Web Development? If so, be sure to check out my recommended platform to learn coding online:
Implementing a basic debounce function
Debounce is implemented using a timer, where the action executes after the timeout. If the action is repeated before the timer has finished, we restart the timer and queue the latest action.
Here's the most basic implementation in TypeScript:
Create a function that accepts a function to debounce and the timeout delay as arguments.
The debounce function returns a new function. When it's executed, it creates a timer to execute the original function after the delay and cancels the previous timer.
Here's how to use it:
Flushing the debounce result
What if we sometimes need to run the action before the delay and cancel any pending executions? We call that flushing.
We can attach an extra method to the original debounce function implementation, that runs the pending action instantly and clears the timer:
Store the arguments of the last action into an array when calling the debounced function.
Create a new debounce.flush function that runs the action with the most recently used arguments and clears the timer and cached arguments.
Call debounce.flush() to run the action immediately:
This implementation is still quite basic because, in the real world, you may want the debounce function to additionally handle these:
Always run the first call (greedy/eager debounce);
To cap the delay, in case the action is long-running and continuous and we want the function to run at some point (a mix between debounce and throttle).
An ability to clear the queued action, without running it.
Feel free to extend the implementation to add these considerations yourself. If you're fine with depending on a 3rd party library, there is a good implementation on GitHub, or by lodash.
What is throttling?
Throttling is a technique used to improve the performance of frequently executed actions, by limiting the rate of execution. It is similar to debounce, except it guarantees the regular execution of an action.
The most common use case from my experience is to optimize the resize and scroll handlers. That's especially important in React because they often trigger state updates that are responsible for making your components re-render.
The solution to this problem is to call the handlers intermittently. Most of the time we don't need to keep 100% in sync with the resize or scroll events, so we can throttle the handler functions. I like to think of them as lossy event handlers.
Use the throttle function when you care about some intermediate values of a frequently executed expensive action, but it's ok to discard most of them.
Implementing a throttle function
The throttle function is implemented using a timer that puts the throttled function on cooldown:
Create a throttle function that accepts a callback and the cooldown duration arguments.
The throttle function returns a new function, which when executed, stores the call arguments and starts the cooldown timer.
When the timer finishes, execute the action with the cached arguments and clear them.
Here's how you would use it:
Similarly to debounce, you may need some extra flexibility, which isn't hard to implement yourself, but you may consider using a 3rd party library for:
Execute the action initially (currently, it's delayed);