Debounce and Throttle - In-depth Guide

throttle and debounce are two functions widely used in frontend applications to filter a stream of events. They both optimize the application and browser performance.

Debounce

Debounce function limits the execution of a function call and waits for a certain amount of time before running it again.

Imagine a search form. Whenever a user types something we can perform a network request to fetch the search results. However, performing a network request after each character is typed is too expensive.

We can optimize and reduce the count of API calls by debouncing logic. Here we monitor the delay user gives between two key presses. If this delay matches our threshold limit, then we make another API call.

You may find it irritating that the debouncing event waits before triggering the function execution until the events stop happening so rapidly. Why not trigger the function execution immediately, so it behaves exactly as the original non-debounced handler? But not fire again until there is a pause in the rapid calls.

Lodash supports this with two optional parameters:

  • trailing: the function should be run after some time without running the function
  • leading: the function is run immediately and then it’s not run even if we call it again

Another Example of Debounce is when resizing a (desktop) browser window, they can emit many resize events while dragging the resize handle. Debounce Resize Event Example

Stream of events as they happen and as they trigger debounced handlers

Throttle

Throttling is a technique, to limit the execution of an event handler function, even when this event triggers continuously due to user actions.

Stream of events as they happen and as they trigger throttled handlers

Infinite Scroll is a quite common example. The 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 more content and append it to the page. Here debounce wouldn't be helpful. It only would trigger only when the user stops scrolling. With throttle, we are constantly checking how far we are from the bottom. Infinite Scrolling throlled


The demo below showcases a stream of events and how debounced and throttled handlers are called. This codepen really helps visualize how they work with different leading and trailing options.

Debounce Polyfill

function debounce(func, wait, option = { leading: false, trailing: true }) {
    let timerId = null;
    let lastArgs = null;
    return (...args) => {
      // if both leading and trailing are false then do nothing.
      if (!option.leading && !option.trailing) return;

      // if timer is over and leading is true
      // then immediately call supplied function
      // else capture arguments in lastArgs
      if (!timerId && option.leading) {
        func.apply(this, args);
      } else {
        lastArgs = args;
      }

      // clear timer so that next call is exactly after `wait` time
      clearTimeout(timerId);

      timerId = setTimeout(() => {
        // invoke only if lastArgs is present and trailing is true
        if (option.trailing && lastArgs) func.apply(this, lastArgs);

        // reset variables as they need to restart new life after 
        // calling this function 
        lastArgs = null;
        timerId = null;
      }, wait);
    }
  }

Throttle Polyfill

const throttle = (func, wait, options = {leading: true, trailing: true}) => {
  let timer = null
  let lastContext = null
  let lastArgs = null
  return function(...args) {

    // 1. if called within cool time, then store it for later call
    if (timer !== null) {
      lastContext = this
      lastArgs = args
      return
    }

    // 2. if other than cool time, execute it
    if (options.leading !== false) {
      func.call(this, ...args)
    } else {
      // save for trailing call if needed
      lastContext = this
      lastArgs = args
    }

    // 3. set a timeout to clear the cool, time
    // and run the stored context
    const timeup = () => {
      if (options.trailing !== false && lastArgs !== null) {
        func.call(lastContext, ...lastArgs)
        lastContext = null
        lastArgs = null
        timer = setTimeout(timeup, wait)
      } else {
        timer = null
      }
    }

    timer = setTimeout(timeup, wait)
  }
}

Feel free to leave your thoughts in the comments.

I love to connect with new people. Hit me up on Twitter | Linkedin

8
Subscribe to my newsletter

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

Written by

Bhavika Tibrewal
Bhavika Tibrewal

I am a FullStack Developer who loves to build projects that solve real-world problems. Sharing my journey and learnings here with my fellow developers.