DEV Community

Cover image for JavaScript Debouncing from Scratch
Eihab Khan
Eihab Khan

Posted on • Edited on

3 1 1 1

JavaScript Debouncing from Scratch

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
Enter fullscreen mode Exit fullscreen mode

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 {
}
Enter fullscreen mode Exit fullscreen mode

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() {
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
  };
}
Enter fullscreen mode Exit fullscreen mode

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!

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay