DEV Community

Cover image for Debounce your way through tough resource-intensive tasks in JS
Sobhan Dash
Sobhan Dash

Posted on

Debounce your way through tough resource-intensive tasks in JS

Most of us have worked with resource-intensive tasks or server calls in JavaScript, and we also have encountered situations where we need to regulate the number of times a function is invoked.

Debounce helps us to do the exact thing. We can use the debounce() function to wait a certain time before running again.

A lengthy way of doing the same is using the setTimeout/clearTimeout functions in the order:

  1. Cancel the previously running timer.
  2. Start a new timer.
  3. Run the delayed code on timeout.

For example, let's create a autocomplete search bar.

const search = document.createElement('input');
const suggestions = document.createElement('div');

// Declare timer
let searchTimer;
search.addEventListener('input', () => {

  // Clear timer
  clearTimeout(searchTimer);

  // Start a new timer
  searchTimer = setTimeout(() => {

    // Run slower code
    fetch(`/api/search?query="${encodeURIComponent(search.value.trim())}"`)
      .then((response) => response.json())
      .then((body) => {
        suggestions.textContent = body.join(', ');
      });

  }, 300);

});

search.type = 'search';
search.placeholder = 'Please enter your query...';
document.body.appendChild(search);
document.body.appendChild(suggestions);
Enter fullscreen mode Exit fullscreen mode

Another example of debouncing can be found in this interactive pen Debounce Visualizer

Now a question arises,

Why do we need Debounce?

As I said, there are several input signals in JavaScript frontend apps. These are usually in the form of events from various user inputs such as the user mouse movements, keyboard inputs, or resizing the browser window.

So like the above example where we need to talk to the server for fetching the appropriate data, constantly changing input will trigger the time-consuming functions more often than the browser can manage. Debouncing will reduce the number of calls made to the server.

Another reason to debounce is that the updates of the user interface might become too frequent. You might have experienced flickering in your UI. This can occur when an area is dependent on the user input, and the value changes frequently. It can be seen while a user is typing or when the validation error tooltips update on each user input. They give a "flickering" impression.

How long should the debounce timeout be?

It could be anything you want, but a standard time has been set under some rules.
For fluid updates and a smooth user experience, a delay of 30 milliseconds is a sweet spot.
If you have played any game, you know that 30fps is fairly smooth too, and would give us 33.33 milliseconds per frame. (1000 / 30 = 33.33)

Delaying no more than 30 milliseconds is usually barely noticeable but still greatly debounces things like window resize or scroll events. These debounce delays can therefore be added fairly freely to your code without having too much negative impact on your application.

Similarly, for keyboard input and window resize, a delay of 300 milliseconds is a good number. Since it is a longer duration than the previous one, the delay will be visible. The users can experience the lag in UI. However, this is perfect for use cases like searching. If the duration is decreased the user will trigger searches too frequently.

Note: The total time to present the suggestions in the above example will be total time = debounce delay + server latency + server database lookup.

Adding the debounce function can worsen the user experience. So try to be mindful of where it is absolutely required. The 300 milliseconds debounce delays can also be used for resizing the window. While resizing, we trigger many events and often trigger reflow operations because of layout changes.

The delay thus prevents the app from locking up while the window is being resized. However, we should always try to fix using CSS rather than JavaScript. Using media queries or grid/flex is more appropriate.

How to create a reusable debounce function?

Now adding clearTimeout/setTimeout pairs throughout the code is sometimes good enough. We can also do this without keeping track if the time variables. Thus, we can keep it inside a function scope:

function debounce(callback, delay) {
  let timeout;
  return (...args) => {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => callback.apply(context, args), delay);
  };
}
Enter fullscreen mode Exit fullscreen mode

Using the debounce function, we can rewrite the first example in a shorter form as:

const search = document.createElement('input');
const suggestions = document.createElement('div');

search.addEventListener('input', debounce(() => {

  fetch(`/api/search?query="${encodeURIComponent(search.value.trim())}"`)
    .then((response) => response.json())
    .then((body) => {
      suggestions.textContent = body.join(', ');
    });

}, 300));

search.type = 'search';
search.placeholder = 'Please enter your query...';
document.body.appendChild(search);
document.body.appendChild(suggestions);
Enter fullscreen mode Exit fullscreen mode

This will keep your code delayed yet performant.

Do you use debounce functions in your code? How is it better than setTimeout/clearInterval? Let me know👇🏻

Discussion (0)