Hone logo
Hone
Problems

Implement Debounce

Debouncing is a common technique used to control how often a function is executed. It's particularly useful for events that fire rapidly, like scrolling, resizing, or typing in a search bar. The goal is to ensure a function is only called after a certain period of inactivity, preventing excessive computations.

Problem Description

Your task is to implement a debounce function that takes two arguments:

  1. A function (func) to be debounced.
  2. A delay time (delay) in milliseconds.

The debounce function should return a new function. When this new function is called, it should not immediately execute func. Instead, it should reset a timer. If the new function is called again before the delay has elapsed, the previous timer should be cleared, and a new timer should be started. Only when the delay has passed without any further calls to the new function should func be executed.

The debounced function should also be able to accept any arguments that func would normally receive and pass them along when func is eventually executed. The this context should also be preserved.

Key Requirements:

  • The returned function should accept arbitrary arguments and preserve this context.
  • The debounced function should only execute the original func after the specified delay has passed without further invocations.
  • If the debounced function is invoked multiple times within the delay period, the previous pending execution of func should be canceled.

Expected Behavior: Imagine a user rapidly clicking a button. With debouncing, the associated action would only trigger once, after the user stops clicking for a brief moment.

Edge Cases:

  • What happens if delay is 0 or negative?
  • What happens if func throws an error? (For this challenge, assume func will execute successfully).

Examples

Example 1:

// Assume a simplified clock with millisecond precision for demonstration

let callCount = 0;
const logMessage = (message) => {
  callCount++;
  console.log(`(${callCount}) Executed with: ${message}`);
};

const debouncedLog = debounce(logMessage, 100);

// Simulate rapid calls
debouncedLog("first");  // Timer starts, but logMessage is not called yet
// ... 50ms pass ...
debouncedLog("second"); // Previous timer cleared, new timer starts
// ... 70ms pass ...
debouncedLog("third");  // Previous timer cleared, new timer starts

// ... 100ms pass after "third" without further calls ...

// Expected Output (after approximately 220ms from the first call):
// (1) Executed with: third
// Explanation: The "third" call reset the timer. Since no more calls were made for 100ms,
// logMessage was executed with the arguments from the "third" call.

Example 2:

let actionCount = 0;
const performAction = function(item) {
  actionCount++;
  console.log(`Action performed for: ${item} (Call #${actionCount}). Context: ${this.id}`);
};

const debouncedAction = debounce(performAction, 200);
const contextObject = { id: "context123" };

// Simulate calls with context
debouncedAction.call(contextObject, "itemA"); // Timer starts
// ... 150ms pass ...
debouncedAction.call(contextObject, "itemB"); // Previous timer cleared, new timer starts

// ... 200ms pass after "itemB" without further calls ...

// Expected Output (after approximately 350ms from the first call):
// Action performed for: itemB (Call #1). Context: context123
// Explanation: The "itemB" call reset the timer. After 200ms of inactivity, performAction
// was executed with "itemB" and the correct 'this' context.

Example 3: (Showing cancellation)

let invocationCount = 0;
const costlyOperation = (data) => {
  invocationCount++;
  console.log(`Costly operation executed with data: ${data}. (Invocation #${invocationCount})`);
};

const debouncedOperation = debounce(costlyOperation, 500);

debouncedOperation("initial data"); // Timer 1 starts
// ... 200ms pass ...
debouncedOperation("updated data"); // Timer 1 cancelled, Timer 2 starts
// ... 300ms pass ... (Total 500ms elapsed since last call)
// No further calls.

// Expected Output (after approximately 700ms from the first call):
// Costly operation executed with data: updated data. (Invocation #1)
// Explanation: The second call to debouncedOperation cancelled the first pending execution.
// The operation was then executed with the data from the second call after its timer expired.

Constraints

  • The delay parameter will be a non-negative integer representing milliseconds.
  • The func parameter will be a valid function.
  • The implementation should be efficient and avoid unnecessary resource usage.
  • The solution should work across different JavaScript environments (e.g., browser, Node.js).

Notes

  • You will need a mechanism to schedule and cancel timers. In JavaScript, setTimeout and clearTimeout are the standard tools for this.
  • Consider how to store the timer ID so that it can be cleared later.
  • Think about how to capture and pass the arguments and this context correctly.
Loading editor...
plaintext