Debounce Coding Interview Question | Skilled.dev
Interview Question

Debounce

Course launching November 17, 2020

Follow me on YouTube for free coding interview videos.

Users who sign up for the email list will receive an exclusive 75% discount at launch.

Your subscription could not be saved. Please try again.
Your subscription was successful! 🤓
Loading...

Have you noticed that when you type into a search input, there will be a delay before the typeahead results appear. This functionality is frequently controlled by a function called a debounce (it could also be a throttle function that has a similar outcome). The debounce function delays the processing of the keyup event until the user has stopped typing for a predetermined amount of time.

This prevents your UI code from needing to process every event and also drastically reduces the number of API calls sent to your server. Processing every character as it's entered could harm performance and add unnecessary load to your backend.

Let's see a more obvious example and compare it to what we would expect without debouncing.

Implementing a debounce from scratch is a common interview question. It tests your understanding of intermediate and advanced JavaScript concepts such as: async programming, callbacks, scope, and closures. It is also a practical solution used in real-world applications to improve performance and demonstrates that you understand the tools to write good code for real users.

A debounce is a cousin of the throttle, and they both improve the performance of web applications. However, they are used in different cases. A debounce is utilized when you only care about the final state. For example, waiting until a user stops typing to fetch typeahead search results. A throttle is best used when you want to handle all intermediate states but at a controlled rate. For example, track the screen width as a user resizes the window and rearrange page content while it changes instead of waiting until the user has finished.

Write a function debounce that takes a callback function and a wait time and prevents the callback from executing until the wait time finishes.

const WAIT_TIME = 5000; // 5 seconds
const debouncedEventListiner = debounce(() => console.log('I executed'), WAIT_TIME);

// time = 0
debouncedEventListiner();
// wait 2 seconds (time = 2)
debouncedEventListiner();
// wait 2 seconds (time = 4)
debouncedEventListiner();
// wait 2 seconds (time = 6)
debouncedEventListiner();
// do nothing for 5 seconds (time = 11)

// Executes at time = 11 because previous executions reset it
executes callback at                      |
time                     0     5     10   11

Anytime the debounced function is executed, it should restart the timer and only execute once the time finishes.

Let's dive in and see what a debounce looks like:

const debounce = (func, wait) => {
  let timeout;

  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

A debounce is a higher-order function, which is a function that returns another function (named executedFunction here for clarity). This is done to form a closure around the func and wait function parameters and the timeout variable so that their values are preserved. The following is a definition of each variable:

  • func: The function that you want to execute after the debounce time
  • wait: The amount of time you want the debounce function to wait after the last received action before executing func. For our typeahead example, it would be the amount of time to wait after the last key press.
  • timeout: The value used to indicate a running debounce.

We can use a debounce doing:

var returnedFunction = debounce(function() {
  // All the taxing stuff you do
}, 250);

window.addEventListener('resize', returnedFunction);

Since debounce returns a function, the executedFunction from the first example and the returnedFunction function from the second example are the same function. Every time the window is resized, it will execute executedFunction/returnedFunction.

Our executedFunction spreads over the parameters (...args) to allow for the debounce function to receive any number of parameters to pass to the callback.

We declare a callback function named later which is the function that's executed after the end of the debounce timer. This is what will be called after the setTimeout expires.

Next, we clearTimeout which had prevented the callback from being executed and thus restarts the debounce. Then we (re-)declare timeout which starts the debounce waiting period. If the full wait time elapses before another event, then we execute the later callback function. The timeout is set to null which means the debounce has ended. This executes func(...args).

There is a more advanced version of this where we can pass an immediate flag to debounce. Currently we always wait until the end of the debounce to execute the callback, but with immediate, you can change it such that the function executes at the leading edge and won't allow you to execute again until it has delayed calling long enough to deplete the timer.

Common scenarios for a debounce are resize, scroll, and keyup/keydown events. In addition, you should consider wrapping any interaction that triggers excessive calculations or API calls with a debounce.

Here’s a commented version of the function as well.

// Returns a function, that as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being executed for
// `wait` milliseconds.
const debounce = (func, wait) => {
  let timeout;

  // This is the function that is returned and will be executed many times
  // We spread (...args) to capture any number of parameters we want to pass
  return function executedFunction(...args) {

    // The callback function to be executed after
    // the debounce time has elapsed
    const later = () => {
      // null timeout to indicate the debounce ended
      clearTimeout(timeout);

      // Execute the callback
      func(...args);
    };

    // This will reset the waiting every function execution.
    // This is the step that prevents the function from
    // being executed because it will never reach the
    // inside of the previous setTimeout
    clearTimeout(timeout);

    // Restart the debounce waiting period.
    // setTimeout returns a truthy value (it differs in web vs Node)
    timeout = setTimeout(later, wait);
  };
};
Prev
Pub-Sub and Event-Driven Programming
Next
Throttle
Loading...

Table of Contents