What is Debouncing?
Debounce is a technique used to postpone the invocation of a function till a specified amount of time has passed. A key characteristic of “debounced” functions is that even if it gets called multiple times, no call overlaps another.
How to Use Debounce
the debounce function is used to create a new function
let counter = 0
const increment = () => {
counter += 1;
}
const debouncedIncrement = debounce(increment, 500);
debouncedIncrement() // increases counter by 1 after 500ms have passed.
// In case of multiple calls:
debouncedIncrement(); // called immediately
debouncedIncrement(); // called 100ms later
debouncedIncrement(); // called 200ms later
// Only the last call executes, 500ms after the final call
Real-World Applications
To better understand why we need debouncing, let’s take a look at a real-world example where debouncing is used, now this is not the only usage of debounce, but to make this explanation simple, we will talk about one example: Search boxes.
There’s a high chance you’ve already encountered some form of debouncing, especially on websites where users are likely to use a search box; You go to the website, click on the search box, and start typing, and a suggestion/result box appears after some fraction of a second. That right there is debouncing in action.
Building Our Own Debounce Function
TIDBIT: Earlier we mentioned that the debounce function returns a function that you can call that will in turn call your callback function after a time has passed. A hint to look for is that whenever you see a function that returns another function, you’re almost guaranteed that there is some form of closure going on under the hood. Spoiler alert: a closure happens in the debounce function that allows for its “debouncing” feature.
Alright, enough talk, let's have at it, let’s start by writing the function signature
function debounce(callback: Function, wait: number): Function {
}
we know that it will return a function, so let’s return an empty function for now
function debounce(callback: Function, wait: number): Function {
return function() {
}
}
now let’s think about what the debounce function does; wait for some time and then call the callback function, if you’ve been writing JavaScript for a while you know that there is a function that offers this functionality for us, the setTimeout()
function:
function debounce(callback: Function, wait: number): Function {
return function() {
setTimeout(() => callback(), wait);
}
}
Cool, this code seems to work right, but we’re not there yet as we still haven’t achieved the key characteristic of debouncing: Canceling previous debounced calls.
The way to do that is straightforward, setTimeout
returns an id that we can use to clear the timeout using clearTimeout
.
function debounce(callback: Function, wait: number): Function {
return function() {
const timer = setTimeout(() => callback(), wait);
}
}
With the way this code is written, if we call the debounced function multiple times, it will never cancel the previous call, that’s because in each call we don’t know if the function has been called before or not. This is a use case for why closures are useful in JavaScript. Let’s modify the code a bit
function debounce(callback: Function, wait: number): Function {
let timer: number;
return function() {
clearTimeout(timer);
timer = setTimeout(() => callback(), wait);
}
}
This simple change makes a huge impact. Now, if we call the debounced function multiple times, the function knows if it was called before when the timer variable was populated. If it was we simply clear the previous timeout and we create a new one.
Sometimes the debounced function is expected to receive some parameters. There are also times when there is a reference to this
inside the callback. Our function doesn’t support either of these so let’s do that:
function debounce(callback: Function, wait: number): Function {
let timer: number;
return function (this: any, ...args: any[]) {
clearTimeout(timer);
timer = setTimeout(() => callback.apply(this, args), wait);
};
}
We’ve done a couple of things here. First, we added this
to the list of params so we can bind it with the callback function. Second, we captured any params the user might pass to the debounced function and passed them to the callback function.
That’s pretty much it for the debounce function. I want to point out one last thing which is important to know; Notice in the return statement we returned a normal function instead of an arrow function, that’s deliberate.
You see, one distinct difference between normal and arrow functions is the following:
-
this
in arrow functions references the context where the function was defined. -
this
in normal functions references the context where the function was called.
That’s pretty much it. Congratulations! You just built your own debounce function 🎉
Further Reading & Resources
A Personal Note
I originally wrote these notes to solidify my own understanding of debouncing. While writing them, I found that explaining concepts to myself helped cement my learning. I'm sharing them publicly in case they might help others on their coding journey - and maybe because explaining things is just fun.
If you found this helpful or spotted something that could be improved, feel free to leave a comment!
Top comments (0)