Skip to content

Edvins Antonovs

Debounce method in JavaScript code challenge

Problem

Debouncing is a technique used to control how many times we allow a function to be executed over time. When a JavaScript function is debounced with a wait time of X milliseconds, it must wait until after X milliseconds have elapsed since the debounced function was last called. You almost certainly have encountered debouncing in your daily lives before — when entering an elevator. Only after X duration of not pressing the "Door open" button (the debounced function not being called) will the elevator door actually close (the callback function is executed).

Implement a debounce function which accepts a callback function and a wait duration. Calling debounce() returns a function which has debounced invocations of the callback function following the behavior described above.

"Debounce, along with throttle, are among the most common front end interview questions; it's the front end equivalent of inverting a binary tree" - GreatFrontEnd.

Example

1let i = 0;
2function increment() {
3 i++;
4}
5const debouncedIncrement = debounce(increment, 100);
6
7// t = 0: Call debouncedIncrement().
8debouncedIncrement(); // i = 0
9
10// t = 50: i is still 0 because 100ms have not passed.
11
12// t = 100: increment() was invoked and i is now 1.

debouncedIncrement() is called multiple times.

1let i = 0;
2function increment() {
3 i++;
4}
5const debouncedIncrement = debounce(increment, 100);
6
7// t = 0: Call debouncedIncrement().
8debouncedIncrement(); // i = 0
9
10// t = 50: i is still 0 because 100ms have not passed.
11// Call debouncedIncrement() again.
12debouncedIncrement(); // i = 0
13
14// t = 100: i is still 0 because it has only
15// been 50ms since the last debouncedIncrement() at t = 50.
16
17// t = 150: Because 100ms have passed since
18// the last debouncedIncrement() at t = 50,
19// increment was invoked and i is now 1 .

Solution

I came across this code challenge some time ago, I couldn't provide a fully working solution from the first attemp. It was especially hard during the interview while writing it in Microsoft Docs without access to the console to run the code. Apparently I rely too hard on console.log during the implementation process. My second attempt to crack it was on GreatFrontend where I took this challenge again and finally was able to provide a working solution.

Let's have a quick peek at the code:

1/**
2 * @param {Function} func
3 * @param {number} wait
4 * @return {Function}
5 */
6export default function debounce(func, wait = 0) {
7 let timeoutID = null;
8
9 return function (...args) {
10 const context = this;
11
12 clearTimeout(timeoutID);
13
14 timeoutID = setTimeout(() => {
15 timeoutID = null;
16
17 func.apply(context, args);
18 }, wait);
19 };
20}

To begin with, inside the debounce function, a closure is returned. This returned function can be called with any number of arguments (...args). When this returned function is called, it resets a timer (tracked by timeoutID) using clearTimeout, effectively canceling any previous scheduled executions of the func. Then, a new timer is set using setTimeout, delaying the execution of func by the specified wait duration.

After the wait duration has passed without any subsequent calls to the returned function, the scheduled setTimeout callback is triggered. This callback sets timeoutID back to null and invokes the original func with the provided arguments (args). This mechanism ensures that the func is executed only after the rapid succession of calls has stopped, preventing unnecessary or frequent invocations and helping to manage resource-intensive operations more efficiently.


Appendix

© 2024 by Edvins Antonovs. All rights reserved.