Implement debounce in JavaScript
Debounce function limits the rate at which a function can fire.
We want to use debounce
every time we trigger an event/make an API call/run a process that is a heavy task.
For example, an autocomplete. When a user types on an input element we want to send a server request to fetch the data that is relevant to the value the user typed. Imagine we will fire an API call every time a user is typing. Perform an API call is a heavy task so we really don’t want to perform it every time a key was pressed.
What we do want to do?
We want to limit the requests by sending the server more data at once instead less data with more requests.
We want to keep the value of the user input and only when they stop typing we will perform an API call and send the data.

Debounce function limits the rate at which a function can fire.
Side note: debounce and throttle are two similar functions but different. While debounce function limits the rate at which a function can be fire (for example — “execute this function only if 100 milliseconds have passed without it being called.”) throttle enforces a maximum number of times a function can be called over time (for example, “execute this function at most once every 100 milliseconds.”)
Check out this demo that illustrates the difference between debounce and throttle.
Implement a debounce function
Returns a function, that, as long as it continues to be invoked, will not be triggered. The function will be called after it stops being called for X milliseconds.
To get started with the implementation, we need to be familiar with two functions — setTimeout()
and clearTimeout()
.
setTimeout() — The setTimeout()
method sets a timer which executes a function or specified piece of code once the timer expires.
clearTimeout() — The clearTimeout()
cancels a timeout previously established by calling setTimeout()
.
Take a look at the console logs in the codepen example above. setTimeout()
returns an id which we can cancel a timeout using clearTimeout()
by passing that id.
clearTimeout()
cancels the previously operation established bysetTimeout()
.
With that in mind, let’s write some code
The gist above does what we wanted it to do.
We declared a timeout
variable and assigned the myDebounce
function when there is a scroll
event on the window object.myDebounce
function first, clears the timeout variable with clearTimeout()
and then set it to a new id number using setTimeout()
.
It is important to first clear the timeout variable and then assign a new one so we keep tracking the same timeout id
Every time we scroll the window, we are clearing the previously created timeout ID and assign a new one. The minute we stop scrolling, new and last timeout will be created (and will not get cleared) and we will see expensive operation
on the console after 300 milliseconds.
However, this is a BAD code
what? why?
While the code snippet above does the job, it is a bad written code and not recommended.
When we declare a variable in the global scope the variable is accessible from anywhere in the application, meaning we have the ability to use this variable everywhere and most importantly we can assign it a new value from everywhere in our app.
To overcome this issue, we need to enclose this value in its own scope. Whenever we declare a function, we are creating a new scope in the app. Each scope has access to its own variables/functions and manage its own state.
In JavaScript, we have a nesting scope that works one way. Children have access to their parent scope but it is not the other way, parents don’t have access to their children scope.
To have the timeout
variable persists in its own scope, we simply need to create it inside a function. Now before we go and do something like that
const myDebounce = () => { let timeout; clearTimeout(timeout); timeout = setTimeout(() => { console.log(“expensive operation”)}, 300);};window.addEventListener(‘scroll’, myDebounce);
let’s understand why this will not work.
If we would declare the variable inside the function, every time we scroll
the function will get triggered and create a new timeout
variable set to nothing (undefined
), and since when we pass an invalid timer ID to clearTimeout()
, it silently does nothing and clearTimeout(timeout);
becomes useless and we lose the debounce functionality.
What we want to do, is to create the timeout
variable inside the myDebounce
function, but have the function to return an anonymous function.
We just created a closure to handle the private variables our function is holding.
Wait, how come timeout
is being declared only once if we use myDebounce()
every time we scroll?
that is a great question! So while it might seems like we call myDebounce()
every time we scroll
, it is not exactly the case. When the script is loading, it gets to line 11window.addEventListener(‘scroll’, myDebounce())
and initialize myDebounce()
function for the first and only time. The function returns an anonymous function which will be the one that will get triggered every time we will have a scroll
event. myDebounce()
eventually becomes a reference to the anonymous function.
Let’s continue and clean our function a bit. We want the operation setTimeout()
executes to be dynamically passed into myDebounce()
and the delay
time.
Ok, so we have a nice looking working function implementing debounce
functionality.
But hey, it is still a BAD code.
What? what now?
Remember when we were talking about the scope? so now, by looking at the code, we have 3 different scopes and because of it, each function has its own context and therefore its own reference to the this keyword.
1.myDebounce scope — refers to the window object since it is executed globally
2.anonymous function scope-could be window or object - depends how you call myDebounce. In out example, it refers to the window
element (since we trigger the event by scrolling the window)
3.setTimeout scope- refers to the window object
Let’s see an example!
we can assume that we have a button html element with an id
debounceButton
This is the console after running the code and clicking the button once.

on line 6, myDebounce function context, this keyword, refers to the window.
on line 10, the anonymous function context, this keyword, refers to the button (the object that triggers the function).
on line 6, setTimeout context, this keyword, refers to the window.
Another issue is, what will happen if we want to pass N
parameters to myDebounce
function?
as we discussed earlier, the anonymous function
is what being executed every time we call myDebounce
. So while the anonymous function
will get the arguments, eventually, we will want func()
to get them.
those can be retrieved in the anonymous function via a reserved keyword arguments.
We need to bind the anonymous function context into the setTimeout context so when calling the anonymous function the context and arguments would be the same
The apply()
method allows you to call a function with a given this
value and arguments
provided as an array.
when using func.apply(context,args)
you basically tells the func()
method to use the this
as the context
argument, and the args
as the arguments.
The reason we would want to use
apply()
(and notcall()
for example) is becausecall()
accepts an argument list, whileapply()
accepts a single array of arguments
Conclusion
debounce
function is very small, you can change it to your needs but the basic functionality is only about 6 lines of code but still covers a lot of interesting concepts in JavaScript like scope, closure and the lexical context (this keyword).