Debouncing in JavaScript

Brad PrestonBrad Preston
3 min read

What is debouncing?

Debouncing is a programming practice that is used to limit the rate that a function can be executed. In other words, it restricts the number of times a function is ran regardless of how many times it is invoked.

Imagine you have a search bar and every time you type a character in the search bar, it initiates a new search. That could be A LOT of calls to an API and can quickly be expensive if you have many users on your site. This is a prime example of when debouncing could be used.

How to implement debouncing

Debouncing is commonly used for the input, scroll, and resize events. Here, I’m going to be opting for an input event to mimic the example above. Let’s create a simple form with an input for search.

<form>
  <input id="search-bar" placeholder="Search">
  <button>Search</button>
</form>

You could imagine this form to show a quick results dropdown below the search bar or going to the search results page if the form is submitted. Our search form won’t really have any true functionality, but we’ll be using the input as our element to listen for events.

If we didn’t use debouncing, our event listener could look like this:

const input = document.getElementById("search-bar");

input.addEventListener("input", (e) => {
    console.log(e.target.value)
});

The issue is that this will log the value of the input on every key stroke. If the input were to call an API for the quick results, we would be hitting that API several times in under a minute. Even more if you count for typos or users backspacing their entire search query. That is obviously not ideal.

Let’s take a look at how we can create function to handle debouncing.

function debounce(func, timeout = 2500) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, timeout);
  }
}

There is a bit to unpack here. First, our debounce function takes two parameters: a func and an optional timeout. The func parameter takes a function that is to be called after the timeout has finished running.

A timer variable is set as an empty variable to later store the timeout. We then return a function inside of our debounce function which takes the arguments passed to debounce as its own arguments.

The inner function clears the timer if the timer has a time set. Then we set the timer in the outer function body to a new setTimeout function. When the timeout finishes, we call the func with apply to apply the arguments passed into it via the initial debounce func parameter. Otherwise, if the debounce function is invoked again before the timeout has finished, the timer is cleared and reset.

Let’s see our new debounce function in action!

function logSearchValue(e) {
    console.log(e.target.value);
}

input.addEventListener("input", debounce(logSearchValue));

I created an arbitrary function to log the input value. You could replace this logSearchValue function with one that calls an API to gather search results.

Now when we type into our search bar, the results aren’t printed to the console on each key stroke. We get the value of the search bar printed to the console after the timeout runs. In our example, it’s 2500ms or 2.5 seconds. If we begin typing before the 2.5 seconds is up, the value isn’t logged to the console because the timeout has been reset. Only when the timeout has finished will our search value be logged to the console.

0
Subscribe to my newsletter

Read articles from Brad Preston directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Brad Preston
Brad Preston

I'm a software developer with a background in Golang and JS/TS