Debounce
UtilitiesLogic
Implement a debounce function that delays the execution of a function until after a specified amount of time has passed since it was last invoked.
Function Signature:
function debounce(func, wait)
Requirements:
- The function should delay execution until
waitmilliseconds have passed since the last call - Each new call should reset the timer
- Only the last call should execute after the delay period
- Arguments passed to the debounced function should be preserved
Examples
Example 1
Input
const log = () => console.log('Fired!');
const debouncedLog = debounce(log, 1000);
debouncedLog();
debouncedLog();
debouncedLog();
// Wait 1 second...Output
Fired!Explanation
Even though `debouncedLog` is called three times, it only executes once after 1 second because each call resets the timer. Only the final call executes.
Example 2
Input
const greet = (name, greeting) => {
console.log(`${greeting}, ${name}!`);
};
const debouncedGreet = debounce(greet, 500);
debouncedGreet('Alice', 'Hello');
debouncedGreet('Bob', 'Hi');
setTimeout(() => debouncedGreet('Charlie', 'Hey'), 100);
// Wait 500ms after the last call...Output
Hey, Charlie!Explanation
The first two calls are cancelled. The third call (after 100ms) resets the timer, and after 500ms from that call, it executes with the arguments 'Charlie' and 'Hey'.
Constraints
- The function should handle any number of arguments
- The timer should reset on every call
- If called multiple times rapidly, only the last call should execute
Notes
- Use closures to maintain the timer state
- Consider edge cases like `wait` being 0 or negative
- Make sure to clear the previous timer before setting a new one
Hints
Editorial: Debounce
Understanding Debounce
Debounce is a technique that ensures a function only executes after a certain amount of time has passed since it was last called. The key concept is resetting the timer on every call.
Implementation Strategy
- Use a closure to maintain state (the timer ID) between function calls
- Clear the previous timer on each new call
- Set a new timer that will execute the function after the delay
Solution Code
function debounce(func, wait) { let timeoutId = null; return function(...args) { // Clear any existing timeout if (timeoutId) { clearTimeout(timeoutId); } // Set a new timeout timeoutId = setTimeout(() => { func.apply(this, args); }, wait); }; }
Key Points
- Closure: The
timeoutIdvariable is captured in the closure, persisting between calls - Clearing timeout:
clearTimeoutcancels any pending execution - Preserving context: Using
func.apply(this, args)ensures the originalthiscontext and arguments are preserved
Time Complexity
- O(1) for each call - we're just setting/clearing timeouts
Space Complexity
- O(1) - we only store one timeout ID
Common Use Cases
- Search input: Wait for user to stop typing before making API call
- Window resize: Wait for resize to finish before recalculating layout
- Button clicks: Prevent accidental double-clicks
- Form validation: Validate after user stops typing
Edge Cases to Consider
- What if
waitis 0? The function should still be deferred to the next tick - What if the function throws an error? Consider wrapping in try-catch
- What about cancellation? You might want to return a cancel function
Advanced: Debounce with Leading/Trailing Options
function debounce(func, wait, options = {}) { let timeoutId = null; let lastArgs = null; const { leading = false, trailing = true } = options; return function(...args) { lastArgs = args; const shouldCallNow = leading && !timeoutId; if (timeoutId) clearTimeout(timeoutId); timeoutId = setTimeout(() => { timeoutId = null; if (trailing && lastArgs) { func.apply(this, lastArgs); } }, wait); if (shouldCallNow) { func.apply(this, args); } }; }
This advanced version allows:
- leading: Execute immediately on the first call
- trailing: Execute after the delay (default behavior)
</>JavaScript
Loading editor…
Test Result
Run your code, or Submit to test it