Taro Logo

Debounce

Medium
Amazon logo
Amazon

Given a function fn and a time in milliseconds t, return a debounced version of that function.

A debounced function is a function whose execution is delayed by t milliseconds and whose execution is cancelled if it is called again within that window of time. The debounced function should also receive the passed parameters.

For example, let's say t = 50ms, and the function was called at 30ms, 60ms, and 100ms.

The first 2 function calls would be cancelled, and the 3rd function call would be executed at 150ms.

Could you provide the code for the debounce function?

Here are some examples to test your code:

Example 1:

t = 50
calls = [
  {"t": 50, inputs: [1]},
  {"t": 75, inputs: [2]}
]

Expected Output:

[{t: 125, inputs: [2]}]

Example 2:

t = 20
calls = [
  {"t": 50, inputs: [1]},
  {"t": 100, inputs: [2]}
]

Expected Output:

[{t: 70, inputs: [1]}, {t: 120, inputs: [2]}]

Example 3:

t = 150
calls = [
  {"t": 50, inputs: [1, 2]},
  {"t": 300, inputs: [3, 4]},
  {"t": 300, inputs: [5, 6]}
]

Expected Output:

[{t: 200, inputs: [1, 2]}, {t: 450, inputs: [5, 6]}]

Solution


Debounce Function Implementation

This problem requires implementing a debounce function, which delays the execution of a given function until after a specified period of inactivity. This is useful for scenarios like handling rapidly-fired events (e.g., window resizing, typing in a search box) to avoid unnecessary computations.

1. Naive Approach

A straightforward approach involves using setTimeout to delay the execution of the function and clearTimeout to cancel any pending executions if the debounced function is called again within the specified time window.

function debounce(fn, t) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      fn.apply(this, args);
    }, t);
  }
}

Explanation:

  1. timeoutId stores the ID of the timeout, allowing us to cancel it later.
  2. The returned function is the debounced function.
  3. clearTimeout(timeoutId) cancels any previously scheduled execution.
  4. setTimeout schedules the execution of fn after t milliseconds.
  5. fn.apply(this, args) calls the original function with the correct context (this) and arguments.

2. Optimal Approach (Same as Naive)

The naive approach is already quite efficient and serves as the optimal solution for this problem. The key is the proper use of setTimeout and clearTimeout to manage the delayed execution and cancellation of pending executions.

function debounce(fn, t) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      fn.apply(this, args);
    }, t);
  }
}

3. Big O Analysis

  • Time Complexity: O(1) - Each call to the debounced function takes constant time to clear the existing timeout and set a new one. The actual execution of the debounced function fn is not considered part of the debounce function's complexity.
  • Space Complexity: O(1) - The space used is constant, as we only store the timeoutId.

4. Edge Cases

  • t = 0: If t is 0, the function will be executed as soon as possible, but still asynchronously.
  • fn throws an error: The debounce function doesn't handle errors thrown by fn. You might want to add a try...catch block if error handling is important.
  • this context: The apply method ensures the correct this context is passed to the debounced function.

5. Code (JavaScript)

function debounce(fn, t) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      fn.apply(this, args);
    }, t);
  }
}