Decoding JS (Part - 2): Debouncing and throttling

Decoding JS (Part - 2): Debouncing and throttling

A few years ago, an issue came up on a popular social network website rendering it unresponsive on long scrolls or making the process unbearably slow. On investigation, it was found that some very expensive computations were being performed on a scroll event. For, depending upon the browser the scroll event can fire a lot and putting code in the scroll callback will slow down any attempts to scroll the page (not a good idea). This makes us understand that for actions which are in users' control - window resizes, button click, scroll etc. many events may be fired which may not be necessary or need to be regulated and would affect the performance of the website in the long run - Debouncing and Throttling help us here.

Debouncing

In the debouncing technique, no matter how many times the user fires the event, the attached function will be executed only after the specified time once the user stops firing the event.

In the above diagram, the lines represent the events fired, for every event fired, there is a waiting time which will start. However, the function would only be called when the waiting time crosses a specified threshold.

Let's try an implementation of debounce function -

function debounceFunction(callBack, delay = 250) {
  let timeout

  return (...args) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => { 
      callBack(...args) // call the function after delay
    }, delay)
  }
}
const expensiveFunction = () => {
// some expensive calculations here
}

We can debounce the above expensive function using our generic implementation of debouncing.

const optimizedExpensiveFunction = debounceFunction(expensiveFunction, 500)

Our debounceFunction takes in a callback function as the first parameter and delay as the second parameter, it then returns a new function at the end of debounce function which acts like a wrapper for the callback.

On each event fired, the timer is restarted.

Throttling

Throttling is a technique in which, no matter how many times the user fires the event, the attached function will be executed only once in a given time interval.

For throttling, the lines above specify the events performed, if the time interval ends when the event is fired, the function is called immediately or it is called once the time interval gets completed, firing the event doesn't reset the timer.

Let's try an implementation of throttling -

const throttleFunction = (callBack, delay = 1000) => {
  let letsWait = false

  return (...args) => {
    if (letsWait) return

    callBack(...args)
    letsWait = true

    setTimeout(() => {
      letsWait = false
    }, delay)
  }
}
const expensiveFunction = () => {
// some expensive calculations here
}

We can throttle the above expensive function using our generic implementation of throttling.

const optimizedExpensiveFunction = throttleFunction(expensiveFunction, 500)

When we throttle a function, we get a new one, when the throttled function is called, it gets called immediately but it checks for the active time delay flag letsWait , if it is in the delay interval, the function is not called but gets called immediately after the delay time ends.

requestAnimationFrame (rAF)

requestAnimationFrame is another way of rate-limiting the execution of functions.

It can be thought of as a _.throttle(dosomething, 16). But with much higher fidelity, since it’s a browser-native API that aims for better accuracy.

Common cases for use of Debouncing and throttling

Resizing window

On resizing windows, multiple resize events may be called. If any functions need to be called on the user action of window resize, they should be debounced.

Infinite Scrolling

Consider the case when a user is scrolling down your infinite-scrolling page. You need to check how far from the bottom the user is. If the user is near the bottom, we should request via Ajax more content and append it to the page.

We cannot use the debounce function here, because, it would be called only once the user stops scrolling but we need to start fetching new content before the user stops scrolling. We can use throttling here.

When the user is typing in a text box and we need to fetch recommendations from a server based on the typed content, we need not fire a request to the server after some specified time limit but rather after the user takes a break while typing. Debouncing helps here.

TL;DR

Performance is an important consideration while building web applications. Whenever function calls, API Calls or other expensive computations are related to some user events that could be needlessly fired multiple times, they should be regulated. Debouncing and throttling help us achieve that, and in turn, optimize the performance of our applications, preventing them to be laggy or janky.