Debouncing and Throttling

Debouncing and Throttling

Introduction

Javascript is a single-threaded interpreted language with a non-blocking event loop. What this means is that everything runs on a single main thread so it's important to use the thread only when required. Blocking this main thread means subsequent operations have to wait till the main thread is clear, this may result in performance issues.

In this blog, we will look at 2 simple examples to understand what are these issues & how to tackle them thereby optimizing performance. Let's say we have an expensive function, a function that makes an API call and performs some heavy computation and then some significant UI changes.

Example 1 :

Consider an input search field with this expensive function attached to its onChange handler. Say, I want to search for a "refrigerator" and I type this without making any wrong keystrokes. In this case, the expensive function will be called 12 times. You might argue that it is better to call the function as suggestions can be quicker. Now, Imagine 1 million users search something at the same time. Imagine the number of requests a server gets that too when someone doesn't make a mistake in typing. So, as the number increases, the number of requests also increases. But do we need to call this function on every change? We probably don't, we can optimize this by calling the function if the delay between two events(keystrokes) is more than let's say 400ms. In simple words, if the user pauses while typing call the function. This method of performance optimization is called debouncing.

Implementation :

  • Create a function debounce that takes a function and a delay.
  • It returns a function that clears the timeout if it already exists and then starts a new timer of the given delay.
  • If another event occurs before the timer is over then this function is called again the old-timer gets cleared and the new timer starts.
  • If another event does not occur before the timer gets over then the expensive function is called.
const debounce = (func, delay) => {
    let timer;
    return function () {
      clearTimeout(timer);
      timer = setTimeout(() => func(), delay);
    };
  };

Try searching with and without debounce and notice the number of API calls -

Example 2:

Consider a shooting game where to fire a weapon we need to click and this expensive function is attached to its onClick handler. In an intense fighting situation where we want to kill the enemies, we would spam the clicks to fire as many rounds as possible. But, a weapon has its limits - Let's say between subsequent shots, there is a delay of 300ms. But, we won't think of all this we would spam the click. So we need to call the function only where there is a delay of 400ms from the last function call. This method of rate-limiting of the function call is called throttling.

Implementation :

  • Create a function throttle that takes a function and a delay.
  • It returns a function that runs the expensive function only if the flag is true, then set's the flag to false and starts a setTimeout to set the flag to true again after the specified limit.
  • If other events occur before this setTimeout expires then the flag is false and the expensive function is not called.
  • In this way the expensive function is only called when the flag is true, else it has to wait till the setTimeout expires and set's the flag to true
const throttle = (func, limit) => {
  let flag = true;
  return function () {
    if (flag) {
      func();
      flag = false;
      setTimeout(() => (flag = true), limit);
    }
  };
};

Try Spamming the two fire buttons and notice the number of shots fired -

TLDR

DEBOUNCING => call a function if the delay between events is more than d THROTTLING => call a function if the delay between function call is more than d

Note: When it comes to debouncing vs throttling, there is no clear winner, it depends on the situation.