Debouncing in JavaScript

Learn debouncing with real-life examples!

Debouncing is a simple concept made complicated. In literal sense, it means bouncing or delaying your work multiple times. In web development, we use it to delay an action (event) until a certain criterion is met - mostly when a given time period expires. In a bit more complicated terms, Debouncing is used when you want to ensure that an event in a series of events is only handled once, after all the events have been fired.

And how do we achieve that? The answer is JavaScript Closures. "Closure" is a very important concept which comes handy in many situations in your web dev life. So, if you don't know about it, please go understand it from somewhere on the internet (or from my another post here) and I bet, you won't regret it! But if you just want a one-line definition of Closure, here it is:

"Closure" in JavaScript is nothing but a function which returns another function, and this returned function has the context (variable data, etc.) of the parent function even after the parent function has stopped executing.

Now, assuming that you have a fair understanding of Closures, let's move on to our actual topic.

In debouncing, we use simple setTimeout to delay the execution of the event - setTimeout(() => eventHandler(), 1000). But then where's the need of closure here? The closure comes into action when we need to clear the setTimeout. And why do we need to clear the setTimeout? Because setTimeout is just delaying the function call, but it is not cancelling the multiple calls. So, to cancel the multiple invocation of our eventHandler to only use it once, we make use of the "closure" concept.

Now let's try to learn this with 2 real life examples:

Example 1

Scenario: You have a form submit button on your web page and by mistake a user double-clicks it (or clicks multiple times), you would not want the form to submit multiple times, would you?

Code without Debouncing

// assuming that you have a submit button in your HTML code with id="submit-btn"
const submitBtn = document.getElementById("submit-btn");

function handleClick(e) {
    console.log("Submit button clicked!");
    // some business logic here
}

submitBtn.addEventListener("click", handleClick);

In the above code snippet, we have simply added an event handler named handleClick which for now just prints a string on the console. Try it out yourselves and you'll see on every click of the button the console will print "Submit button clicked!" and since we want it to happen only once, let's see the below code with debouncing implemented.

Code with Debouncing

// assuming that you have a submit button in your HTML code with id="submit-btn"
const submitBtn = document.getElementById("submit-btn");

function debouncedClick (fn, delayInMs) {
    let timerId; // will store the id returned by setTimeout

    return (...args) => {
        if (timerId) clearTimeout(timerId);

        // storing the setTimeout id in `timerId`
        timerId = setTimeout(() => {
            // `fn` is `handleClick` in our case.
            // it will be executed after `delayInMs`
            fn(...args);
        }, delayInMs);
    }
}

function handleClick(e) {
    console.log("Submit button clicked!");
    // some business logic here
}

// wrapping our original handleClick
// within the `debouncedClick` and specifying the delay we want.
submitBtn.addEventListener("click", debouncedClick(handleClick, 1000));

If you run the above code and click on the Submit button multiple times quickly, you'll see "Submit button clicked!" only once in the console and that too after 1 second (1000 ms) of the last click.

if (timerId) clearTimeout(timerId); makes sure that the previous invocation of setTimeout is cancelled and then in the next line setTimeout is invoked again with the specified delay. So, for multiple clicks, all the previous invocations will be cancelled and only the last one would stay which will get executed after the specified delay (1 sec in the above case).

Note that debouncing only helps you handle multiple events bombarded within a short period of time (specified by you). The click event on this button will still be available after 1 second of your last click, so make sure to handle that scenario in your business logic (maybe redirect to a new page, etc.).

Example 2

Scenario: You have a search text box on your website. When a user starts typing, you would not want to make an API call for every letter she/he types as it would create a lot of load on your server. You ideally would want to make the API call when you sense your user has stopped typing.

Code without Debouncing

// assuming that you have the search input with id="search" & results section with id="results"
const searchBox = document.getElementById("search");
const results = document.getElementById("results");

// function to execute every time the input changes in the search box
// fetches results from API and updates the results section
async function handleInputChange (e) {
    const searchText = e.target.value;
    const response = await fetch("https://your-api-url/search?q=" + searchText);
    const data = await response.json();
    results.innerHTML = data.map(item => `${item.title}<br>`);
}

searchBox.addEventListener("input", handleInputChange);

In the above example, every single letter typed or even deleted will invoke the API call. Let's see how we can do better using debouncing.

Code with Debouncing

// only including the change or addition required here

function debouncedSearch(fn, delayInMs) {
    let timerId;
    return function (...args) {
        timerId && clearTimeout(timerId);
        timerId = setTimeout(
            () => { fn(...args); },
            delayInMs
        );
    }
}

searchBox.addEventListener("input", debouncedSearch(handleInputChange, 500));

If you include this simple debouncedSearch wrapper function, you'll see that API call is made only when user stops typing for 500ms.

Now, let's say a website has 10,000 users who search at least once a day, and they type at least 5 letters. This would result in at least 50,000 searches per day for the website, i.e., 50,000 API calls for search. Now, if the website implements debouncing and assuming that the users type those 5 letters in one go, the website will make only one call per search, i.e., only 10,000 API calls per day. So, we are decreasing the server load by 80%. This is huge!

And for bigger companies like Google, Amazon, etc. the search volumes per day would be in millions and billions, just calculate how much load and how much money they would be saving by implementing Debouncing.

That's it! Thanks for reading. I hope this helped you in understanding the concept clearly. Please share it with your team/colleagues/friends who may benefit from this. See you in the next one!

Did you find this article valuable?

Support Shubhaw Kumar by becoming a sponsor. Any amount is appreciated!