Your Guide to Debouncing in JavaScript (with examples)
Introduction
Any time you visit a website or application, you are most likely experiencing the implementation of “debouncing”. It can be used for preventing multiple submissions on button clicks (forms, payments, etc.), delaying search queries in a search bar, limiting the number of auto-saves to a document, and more.
Debouncing can prevent your application from getting bogged down by repetitive and unnecessary actions and understanding how to implement it in your next program can significantly improve the user experience.
Before diving in, it’s important that you have a solid understanding of Scopes and Closures as this concept plays an integral role in the code we are about to unpack. If you’re unfamiliar, or just need some review, check out Codesmith’s “Hard Parts” lecture on the subject as well as its unit in CSX.
In this article, we will cover:
3. FAQs on debouncing in JavaScript
What is debouncing in JavaScript?
Debouncing is a technique that’s used to keep an event handler from “bouncing” (executing) before a certain amount of time has passed regardless of how many times an event is triggered. In other words, a function (event handler) is “delayed” until the event (like a “click” or “scroll”) stops firing for a certain amount of time.
Let’s say your website requires a user to fill out a form. You want to save the user’s inputs once they have stopped typing for a certain amount of time (say one second). You do this because you want to minimize the amount of saves on that form as each one costs you a database trip. In this case, you would write a debouncer to avoid saving on every single keypress.
Let’s visualize this with the example of saving a document.
Above, we use “getElementById” to select the button element with the id of “app”. We add an event listener (“click” being the event) and an event handler (the anonymous function that will log “document saved!” when that event is triggered).
Because all we’ve done is add an event listener to our button, the event handler will fire each time the button is clicked. If we were actually saving a document on each click event, this could cause quite a few unnecessary trips to our database.
This is where debouncing our JavaScript comes in. Instead of firing our event handler each time the event is triggered, we can write some code that will set a timer.
This timer will not allow the event handler to execute until a user has stopped clicking for a certain amount of time. In the example below, our event handler will not log “document saved!” until the user has stopped clicking for one second.
Throttling vs. debouncing in JavaScript
While the focus of this article is on debouncing in JavaScript, a similar technique called throttling is often used to control the frequency of function invocations and it’s important to understand the difference between the two.
While debouncing is a technique that will delay the execution of a function until an event (like a click) stops firing for a specified time period, throttling is implemented to limit the execution of a function to occur once within a specified time frame regardless of how many times an event is triggered. Let’s use our “Save” button from the example above in this scenario.
With throttling, this button will only log “document saved!” to the console once within a one second period, regardless of how many times it is clicked. See a side by side comparison below with debouncing on the left and throttling on the right.
Notice above how the debounced button will wait one second after the user stops clicking to log “document saved!”, but the throttled version will log “document saved” once per second regardless of how many times the button is clicked.
Example of debouncing
Let’s take a step by step look at the debounced JavaScript code for the example above. We start by defining our function “saveDoc”.
Next, the “debounce” function will receive two arguments:
- A function to be invoked (“saveDoc”).
- A number that represents how many milliseconds to wait before invoking “saveDoc” (1000).
This means that “saveDoc” will delay the execution of saveDoc until the specified amount of time (1000ms in this case) has passed.
In this new, debounced version, our event handler will not be “debounce” but instead will be the evaluated result of invoking “debounce” passing in “saveDoc” and 1000. In other words, “debounce” is being invoked when our code runs- not when a user clicks. When a user clicks, the function returned from “debounce” will run as our event handler. Let’s take a look.
Below, we define our “debounce” function. “debounce” returns an anonymous function. This anonymous function (everything between lines 27 and 38) is what will actually run when a user clicks.
Each time the user clicks, the previous “timeout” is canceled using “clearTimeout(timeout)” on line 30.
You can think of this as canceling our timer. Then, using “setTimeout”, a new anonymous function is scheduled to invoke “func” (“saveDoc”) after “waitTime” (1000 milliseconds).
You can think of this as resetting our timer. Thus, “saveDoc” will only run once our user stops clicking for 1000 milliseconds (1 second). Let’s break this down even further.
1. We start by declaring a variable “timeout” and leave it undefined. Later, this variable will serve as a reference for the invocation of “setTimeout()”.
2. We return a function definition. This is where things get tricky- especially if you aren’t familiar with Scopes and Closures. This returned function is what will actually be executed when a user clicks and, when it’s called, it will have access to the “timeout” variable as well as the arguments paired with “func” and “waitTime”.
3. This anonymous function will immediately clear the previous “timeout” by passing it into “clearTimeout”. This will cancel the execution of “func()”.
4. This is where the debouncing effect occurs in our function. Each time a user clicks, we clear our timer (as seen above). We then reset our timer by invoking “setTimeout” and passing in an anonymous function that will call “func” after “waitTime” has passed. Because we are assigning this functionality to “timeout”, we can reference it as you see above on line 30.
5. Below is a representation next to the actual code to help visualize what’s happening here. Whenever a user clicks the button, “timeout” will wait for 1000ms before invoking “saveDoc”. If the button is clicked before 1000ms is up, our timer will reset and “timeout” will wait another 1000ms. Only once 1000ms has passed without a new button click will “timeout” invoke “saveDoc”.
Now, you may be wondering, “Can’t we just use ‘setTimeout’ instead of clearing ‘timeout’ each time?”
Unfortunately, simply using “setTimeout” to schedule the invocation of “func” isn’t enough in this approach to debouncing our JavaScript code.
Without invoking “clearTimeout(timeout)” on each click, our code would delay the invocation of “func”, but would still end up executing it multiple times, rather than only invoking it once after the user stops clicking.
This would defeat the purpose of debouncing, which is to limit the rate at which the function is executed. Notice below how the invocation of our logging is delayed, but still fires multiple times.
The code above is just one example of debouncing in JavaScript. Another common use case would be in a search bar. Have you ever typed a few characters in a search bar, stopped for a moment, and seen suggested searches based on your input? That’s debouncing!
In the case of a search bar like above. Debouncing can be used in the following way:
- Each time the user types, a timer is started (or reset).
- If the event keeps firing (the user keeps typing), the function call is delayed until the event stops for the specified amount of time.
- Once the user stops typing for a specified time, the function is executed. In this case, the function being executed after the specified time is a fetch request to an API to try and complete the user’s input. Instead of making a request after every keystroke, we can wait until the user stops typing for a brief moment, and then make a request.
Conclusion
You will most likely come across many approaches to debouncing JavaScript code throughout your career- some leveraging the “this” keyword (check out Codesmith’s “Hard Parts” lecture on Object Oriented Programming), some requiring a stronger understanding of the “timeout ID” when using “setTimeout”, many utilizing the “spread operator” to pass multiple arguments into the event handler.
There are a ton of approaches for a ton of use cases (clicking, scrolling, resizing, saving, and much more). However, throughout all of these variations, the idea remains the same- clear and reset a timer until the user stops interacting with an event. The more comfortable you get with our basic example in this article, the more fun you’ll have debouncing your JavaScript to fit your own needs. Happy debouncing!
More on debouncing in JavaScript
What is debouncing in JavaScript?
Debouncing in JavaScript is a technique used to control how often a function executes in response to frequent events such as button clicks, scrolls, resizes, etc. Rather than invoking the function on each event, debouncing delays the execution until the event has stopped for a specified amount of time. This prevents excessive computations or network requests and ensures that your application runs efficiently.
What is an example of debouncing?
Debouncing is often used in search bars with an auto-complete feature to prevent too many API requests while a user is typing. In this case, requests are made to an API to return a list of suggestions that match the search bar’s current input. Instead of sending that API request on each keystroke, debouncing can be implemented to wait until the user has stopped typing for a short period of time before requesting a list of auto-complete suggestions.
What is the difference between throttling and debouncing in JavaScript?
Throttling enforces limits on how often functions can be executed within a specified time frame while debouncing waits for an event to stop for a certain amount of time before executing a function. Let’s break this down.
Throttling is used to invoke a function only once within a specified time period, no matter how many times the event that triggers it occurs. For example, if a button click is used to fire an event (such as saving a document to a database), throttling can be implemented to only save that document once within a certain time frame, regardless of how many times the button is clicked.
Debouncing in JavaScript is used to delay the execution of a function until the event that triggers it stops for a certain amount of time. For example, if a user is typing in a search bar, debouncing can be used to make an API request for a list of auto-complete suggestions once the user has stopped typing for a specific amount of time rather than making the request on every keystroke.
What is debounce()?
Debouncing is used to prevent a function from executing until a set time has passed since its last invocation. This makes sure that the function will not run until a user stops triggering the event that invokes it (like a button click or scroll) for a certain amount of time.
For example, debounce is used for auto-completion of a search bar once a user stops typing for a set time (say 100 milliseconds). Once the user stops typing and 100ms have passed, our function (let’s call it “autoComplete”) will make a request to an API for a list of auto-complete suggestions.
- The first invocation of “autoComplete” is referred to as the “leading edge”
- If “autoComplete” is triggered again before 100ms have passed, it will need to wait another 100ms.
- If 100ms have passed since the last invocation of “autoComplete”, we have reached what is referred to as the “trailing edge” and “autoComplete” is available to run again.