Event Debouncing and Throttling in JavaScript

Key stroke, click, resize, scroll events, etcetera add performance overhead to applications if we respond to these events each time they are fired. The aim is to limit the number of times a function is called in response to events.

In this article, I’ll explain how debounce and throttle works and how to use these rate-limiting techniques to improve the performance of your applications. Request Animation Frame is also another way to handle rate-limiting but we will not cover it in this article.

Debounce

Debounce is a rate-limiting technique that forces a function to execute only once after a set time. It is a higher-order function, i.e. a function that returns another function. It forms a closure around the function parameters.

Sample use cases:

  • Updating the search result as the user types in a search term.
  • Validating an input field as the user inputs a value.
  • Automatically saving contents of a page as the user types.
  • Updating details of selected items as the user modifies the selection.
  • Updating contents of a cart as the user clicks the add to cart button several times in succession.

When a user inputs a search term into a search field and a function is called for each keystroke, it harms performance and add unnecessary load to the backend services. Debounce solves this problem. It delays the function invocation until a certain number of keystrokes are recorded or the user stops typing, thereby optimizing resources.

We have two methods of debouncing:

  • Trailing Debounce
  • Leading Debounce

Trailing Debounce

This is the default way of debouncing. It postpones execution until after an interval has elapsed since the last time the function was invoked. Simply put, it groups successive events and sends the requests as one to the backend service.

/**
 * @param {*} ctx The context
 * @param {function} func The function to execute after the debounce time
 * @param {number} delay The amount of time to wait
 * @return {function} The debounced function
 */
export const debounce = (context, func, delay) => {
  let timeout;

  return (...arguments) => {
    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(() => {
      func.apply(context, arguments);
    }, delay);
  };
};

Our function passed as a parameter spreads its parameters to allow the debounce function receive any number of parameters to pass to the callback.

Once you pass the function and set the time interval, a debounced function is returned that you can pass to the event listeners.

const debouncedSearchState = debounce(
  this,
  searchTerm => getSearchResult(searchTerm), // an expensive function
  1000
);

The code snippet below calls the debounced function in response to the onChange event handler, debouncedSearchState after the time interval has elapsed.

onChange={debouncedSearchState(searchTerm)}

Leading Debounce

You may prefer not to keep the user waiting but trigger an action in response to an event immediately. Then, stop triggering in response to successive requests until there is a pause in the requests.

View the code on CodePen

Throttle

Throttle prevents a function from executing more than once every specified amount of time. It guarantees the function execution once every specified amount of time. Throttle is unlike debounce that do not execute the function if there is no pause in successive requests made.

Sample use case:

  • Requesting more content when the user is close to the bottom of a page during an infinite scroll.
  • Checking the scroll position at a set interval to trigger a CSS animation.
  • Preventing a function call when a button is clicked to avoid spam click.
const throttle = (context, func, limit) => {
  let lastFunc;
  let lastRan;
  return (...arguments) => {
    if (!lastRan) {
      func.apply(context, arguments);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if (Date.now() - lastRan >= limit) {
          func.apply(context, arguments);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
};

Conclusion

Use debounce and throttle to optimize your JavaScript event handlers and improve your application performance and user experience. Both are similar but have their use cases.

Conclusively, use debounce to group successive events and throttle to guarantee function execution once every specified amount of time.

Article Tag  Performance

Share this article:

Stay Updated

    More Articles


    I write about accessibility, performance, JavaScript and workflow tooling. If my articles have helped or inspired you in your development journey, or you want me to write more, consider supporting me.