A couple of years ago, I was asked to write my own debouncer function at an interview. Unfortunately, I didn't have enough time to complete this question, so, I wanted to spend sometime to solve this problem on my own.
So, what is a debounced function?
A debounced function is a delayed function that can only be invoked if a specified amount of time has passed between successful calls. This is often useful when you want to prevent the number of API calls that are made when implementing something like an autocomplete field.
What is a debouncer function?
A debouncer function is a function that takes two arguments -
- a callback (i.e. the function you want to debounce)
- an amount of time in milliseconds between successful calls
and returns a new "debounced" version of the function passed in as the first argument.
The Code
As aforementioned, we want to return a new function from the debouncer:
function debounce(callback, timeout) {
return (...args) => {
// ...
}
}
Since the new function can contain any number of arguments, we will collect all arguments in an array by using the ES6 spread syntax which we will call args
.
Now, we want to "delay" the function call of callback
by timeout
milliseconds. This part is easy, since we can use setTimeout
to simulate a delayed function call.
function debounce(callback, timeout) {
return (...args) => {
setTimeout(() => {
callback.apply(null, args)
}, timeout)
}
}
We are getting close but, you will immediately notice a bug if you try to use our debouncer function in its current state.
<button id="debounced-button">click me</button>
const debouncedHandler = debounce(() => console.log('Clicked!'), 1000)
const button = document.getElementById('debounced-button')
button.addEventListener('click', debouncedHandler)

Everytime we click the button, we create a new setTimeout
that will invoke the callback as soon as timeout
milliseconds has passed. Therefore over time, our callback will be invoked as many times as we clicked it. This is not the intended behavior. We need to figure out a way to "cancel" any previous setTimeout
s, so, we only successfully invoke callback
after the delay has entirely elapsed without interruption. Canceling a setTimeout
is easy using the clearTimeout function that will clear a setTimeout
so long as you pass the unique ID returned from the setTimeout
. But how do we save the previous setTimeout
? I think this is the real challenge of this interview question. It is actually quite simple! We just need to make use of function closures.
function debounce(callback, timeout) {
let timeoutRef
let value
return (...args) => {
clearTimeout(timeoutRef)
timeoutRef = setTimeout(() => {
value = callback.apply(null, args)
}, timeout)
return value
}
}
Let's break this code down further.
- We declare two new variables
timeoutRef
andvalue
outside of the debounced function to hold the ID of thesetTimeout
, and thecallback
return value, respectively. - We clear any stale
setTimeout
by callingclearTimeout
with the ID associated with the previoussetTimeout
. You might be wondering - "But what iftimeoutRef
isundefined
?" For example, on the first run?clearTimeout
will not do anything in that case and will move on to the next line of code. - We save the ID returned from the new
setTimeout
into thetimeoutRef
variable. - finally we save the return value of the
callback
function invoked withargs
, so, that we can return this value from the debounced function (I am using theapply
method on theFunction
prototype, so, that we can invokecallback
with theargs
array. You can read more aboutapply
here.)

And there you have it, a custom debouncer function! This is definitely a great interview question to test the candidates understanding of function closures. I hope this helps you as well in one of your future interviews!