Debounce
Have you noticed that when you type into a search input, there will be a delay before the typeahead results appear.
This functionality is frequently controlled by a pattern called a debounce (it could also be a throttle function that has a similar outcome).
The debounce function delays the processing of the keyup
event until the user has stopped typing for a predetermined amount of time.
This prevents your UI code from needing to process every event and also drastically reduces the number of API calls sent to your server. Processing every character as it's entered could harm performance and add unnecessary load to your backend.
Let's see a more obvious example and compare it to what we would expect without debouncing.
Implementing a debounce from scratch is a common interview question. It tests your understanding of intermediate and advanced JavaScript concepts such as async programming, callbacks, scope, and closures. It is also a practical solution used in real-world applications to improve performance and demonstrates that you understand the tools to write good code for real users.
A debounce is a cousin of the throttle, and they both improve the performance of web applications. However, they are used in different cases. A debounce is utilized when you only care about the final state. For example, waiting until a user stops typing to fetch typeahead search results. A throttle is best used when you want to handle all intermediate states but at a controlled rate. For example, track the screen width as a user resizes the window and rearrange page content while it changes instead of waiting until the user has finished.
Write a function debounce
that takes a callback function and a wait
time and prevents the callback from executing until the wait
time elapses.
Anytime the debounce function is called, it should restart the timer and only execute the callback once the wait
completely elapses.
Let's dive in and see what a debounce looks like:
A debounce is a higher-order function, which is a function that returns another function (named executedFunction
here for clarity).
This is done to form a closure around the func
and wait
function parameters and the timeout
variable so that their values are preserved.
The following is a definition of each variable:
func
: The function that you want to execute after the debounce timewait
: The amount of time you want the debounce function to wait after the last received action before executingfunc
. For our typeahead example, it would be the amount of time to wait after the last key press.timeout
: The value used to indicate a running debounce.
We can use a debounce by doing:
Since debounce returns a function, the executedFunction
from the first example and the returnedFunction
function from the second example are the same function. Every time the window is resized, it will execute executedFunction
/returnedFunction
.
Our executedFunction
spreads over the parameters (...args
) to allow for the debounce function to receive any number of arguments to pass to the callback.
We declare a callback function named later
which is the function that is executed after the end of the debounce timer.
This is what will be called after the setTimeout
expires.
Next, we clearTimeout
which had prevented the callback from being executed and thus restarts the debounce.
Then we (re-)declare timeout
which starts the debounce waiting period.
If the full wait
time elapses before another event, then we execute the later
callback function.
This in turn calls func(...args)
.
There is a more advanced version of this where we can pass an immediate
flag to debounce.
Currently we always wait until the end of the debounce to execute the callback,
but with immediate
, you can change it such that the function executes at the leading edge and won't allow you to execute again until it has delayed calling long enough to deplete the timer.
Common scenarios for a debounce are resize, scroll, and keyup/keydown events. In addition, you should consider wrapping any interaction that triggers excessive calculations or API calls with a debounce.
Here’s a commented version of the function as well.