Debounce functions in JavaScript are higher-order functions that limit the rate at which another function gets called.
A higher-order function is a function that either takes a function as an argument or returns a function as part of its return statement. Our debounce function does both.
The most common use case for a debounce is to pass it as an argument to an event listener attached to an HTML element. To get a better understanding of what this looks like, and why it is useful, let’s look at an example.
Say that you have a function named myFunc
that gets called each time you type something into an input field. After going through the requirements for your project, you decide that you want to change the experience.
Instead, you want myFunc
to execute when at least 2 seconds have passed since the last time you typed something in.
This is where a debounce can comes into play. Instead of passing myFunc
to the event listener, you would pass in the debounce. The debounce itself would then take myFunc
as an argument, along with the number 2000.
Now, whenever you click the button, myFunc
will only execute if at least 2 seconds have elapsed before the last time myFunc
was called.
How to implement a debounce function
From start to finish, it only takes 7 lines of code to implement a debounce function. The rest of this section focuses on those 7 lines of code so that we can see how our debounce function works internally.
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
timeout = setTimeout( callback, delay );
}
}
Starting with line 1, we've declared a new function named debounce
. This new function has two parameters, callback
and delay
.
function debounce( callback, delay ) {
}
callback
is any function that needs to limit the number of times it executes.
delay
is the time (in milliseconds) that needs to elapse before callback
can execute again.
function debounce( callback, delay ) {
let timeout;
}
On line 2, we’re declaring an uninitialized variable named timeout
.
This new variable holds the timeoutID
returned when we call setTimeout
later on in our debounce
function.
function debounce( callback, delay ) {
let timeout;
return function() {
}
}
On line 3, we’re returning an anonymous function. This anonymous function will close over the timeout
variable so that we can retain access to it even after the initial call to debounce
has finished executing.
A closure in JavaScript occurs whenever an inner function retains access to the lexical scope of its outer function, even though the outer function has finished executing. If you want to learn more about closures, you can read Chapter 7 of “You Don’t Know JS” by Kyle Simpson
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
}
}
On line 4, we are calling the clearTimeout
method of the WindowOrWorkerGlobalScope
mixin. This will make sure that each time we call our debounce
function, timeout
is reset, and the counter can start again.
The WindowOrWorkerGlobalScope
mixin of JavaScript gives us access to a few well-known methods, like setTimeout
, clearTimeout
, setInterval
, clearInterval
, and fetch
.
You can learn more about it by reading this article.
function debounce( callback, delay ) {
let timeout;
return function() {
clearTimeout( timeout );
timeout = setTimeout( callback, delay );
}
}
On line 5, we have reached the end of our debounce
function implementation.
That line of code does a few things. The first action is assigning a value to the timeout
variable that we declared on line 2. The value is a timeoutID
that gets returned when we call setTimeout
. This will allow us to reference the timeout created by calling setTimeout
so that we can reset it each time our debounce
function is used.
The second action performed is calling setTimeout
. This will create a timeout that will execute callback
(the function argument passed to our debounce
function) once delay
(the number argument passed to our debounce
function) has elapsed.
Since we are using a timeout, callback
will only execute if we allow the timeout to reach 0. This is where the heart of our debounce
function comes into play since we are resetting the timeout each time debounce
is called. This is what allows us to limit the execution rate of myFunc
.
Lines 5 and 6 contain only brackets, so we won’t go over them.
That’s it. That is how our debounce
function works internally. Now let’s add on to our previous example from the beginning. We’re going to create an input field and attach an event listener with our debounce
function as one of its arguments.
Real World Example
First, we need to create an input field.
<label for="myInput">Type something in!</label>
<input id="myInput" type="text">
Next, we need to create a function that we want to execute whenever we type something into our input field.
function helloWorld() {
console.log("Hello World!")
}
Finally, we need to select the input field we created above and attach a keyup
event listener to it.
const myInput = document.getElementById("myInput");
myInput.addEventListener(
"keyup",
debounce( helloWorld, 2000 )
);
That concludes our real world example! Each time we type something into our input field, helloWorld
will execute if at least 2 seconds have passed since the last time we typed something in.
Special thanks to Reddit user stratoscope for helping to fix some of the initial code in this article. Here is a working demo he created of this debounce
function on Repl.it.
Closing Notes
Debounce functions are simple, yet powerful, functions that can have a noticeable impact on most JavaScript applications.
While our example was fun and straightforward, many large organizations use debounce functions to increase the performance of their applications.
If you want to learn more about JavaScript, check out my website! I am working on some cool stuff at https://juanmvega.com.