CodeX
Published in

CodeX

Software Engineering

Implementing Debounce in Javascript

What’s Debounce and How to Implement It

Image Credit — Workato

If your background is in mobile development, you might already know what debounce is.

In any case, not many heard about it and what problems it solves, but it’s very useful, so let’s dig in.

The term “debounce”, originated first in the electronics and hardware industry.

Mechanical Bounce

Bounce is a property of mechanical buttons and switches, which when pressed introduce electrical noise. This noise obscures the input signal, making the system read it incorrectly.

Source — Pololu Robotics

If you ever worked with microcontrollers and the sorts, you’d understand this issue pretty well.

To explain this simply, imagine you are pressing your light switch in your bedroom. The switch doesn’t make contact with the insides immediately, and there is a little signal bounce to it. This would result in your light getting turned on/off two or three times, before stabilizing.

The Geek Pub has a great article on this. If you are interested more in debouncing in microcontrollers, definitely check it out.

Software Bounce

There is obviously the software counterpart of the bounce issue and solutions for it. Some relate to the software side of microcontrollers, as the issue can be addressed in that approach as well.

Illustration by FreeCodeCamp

However, this story will be about input noise in web applications, backends, and mobile applications, and how this affects performance.

Where Does Input Signal Bounce Occur

The most commons places that you may experience this are:

  • User input fields
  • Buttons and switches
  • Reading message queus

If you worked with message queues, you probably made use of debouncing. Many messaging protocols and libraries allow you to read batches of messages, in a defined time period, which basically is debouncing and aggregating the messages in the background, in order to yield them to the client in a single operation.

In our case at hand, the signal itself will be produced by the user, for example on an input field that has a change listener.

Components such as these can either experience noise, or a large amount of expected input, or they can simply be abused by the end-user.

Let’s consider the following example:

# HTML
<input onKeyUp="inputChanged()" type="text"/>
# JS
function inputChanged() {
console.log("Input event detected.");
}

This is one of the usual cases when you want to listen to user input changes.

Notice how onChange isn’t used, as it emits an event only when focus shifts from the input field.

In this sense, onChange is mitigating the issue on its own. However, it might not be that useful in cases when you want to react in real-time.

The onKeyUp listener emits an event, whenever a user changes the input value. This is often useful for e.g. a real-time search feature.

The issue here is that typing something into a field onKeyUp will produce a lot of events in a short time period rather.

Screencast by Author

If you are filtering some results, the UI will possibly get stuttery and your performance will be affected.

Perhaps you are required to implement a fuzzy search, and the search should happen in real-time, without waiting for the user to hit submit. You need to send HTTP requests in order to retrieve the results, and sending so many requests is obviously not beneficial.

In order to explain the essence of the problem further, let’s go back to the message queue for a moment. For example, you’d configure a Kafka consumer client with your desired batch size, and a time period in which you want to receive each batch.

# Sample pseudo consumer
var consumer = kafka.client()
.batch(100)
.in(1000ms);
consumer.poll();

The above pseudo-code would configure a Kafka client to fetch messages, either every second or as soon as it receives 100 events. You get the idea.

Now, this might be a foundation behind debounce, but there’s a bit more to it. We don’t actually want to consume a batch of events whenever it hits a time threshold or a quantity mark, but we want it once it’s done receiving.

Essentially, with any given ongoing stream, we’ll have trouble deciding that, so in order to mitigate the problem, we want to implement a timeout between two event signals, a time period which if elapses, triggers our batch read.

Solution

With the above said, illustrating debounce, events and the timeout period between two receiving event signals would look like the following.

Illustration by Author

For a debounce method, we need a timeout or threshold time, and we want to trigger an event only once this expires.

Diagram denotes how no execution was triggered when the timer ie. time elapsed between two events was less than the threshold of 1000 milliseconds. We trigger a function, only once the 1000ms elapses.

The last event in the stream will naturally get consumed as well, only on its own in that case, and yet again once the function is triggered after another 1000ms elapses.

In simple terms, we are trying to anticipate the last event in the stream. The threshold time should be tweaked depending on the circumstances. If we’d want to react in more of a timely manner, we’d configure a smaller time threshold. If we want to ensure some logic is only ever triggered once we indeed get the “almost last” event, we’d set it to a higher amount.

Smaller amounts are usually beneficial for responsive user-facing applications. When reading message queues, we perhaps don’t need to read events instantly, so a higher threshold time will save us precious execution time.

There are many libraries in Javascript, and especially in different frameworks.

An easy way how to implement a debounce method is by making use of setTimeout as illustrated in the following snippet:

# HTML
<input id="input-field" onKeyUp="inputChanged()" type="text"/>
# JS
const inputField = document.getElementById("input-field");
function getResult() {
console.log("Input event detected.");
console.log(inputField.value);
// Do something with the input
}
const debounce = {
isWaiting: false,

submit: function (func) {
if (!this.isWaiting) {
this.isWaiting = true;

setTimeout(() => {
func.apply();
this.isWaiting = false;
}, 1000);
}
}
}
function inputChanged() {
debounce.submit(getResult);
}

The debounce object in the above snippet will simply allow us to submit tasks, yet it will detect whether a submission is already waiting to be executed, if yes, simply skip submitting another task.

This is a very naive approach, that doesn’t make use of a threshold timer, but it captures both the issue and a possible solution.

A more sophisticated approach requires us to track the time elapsed between events, as we want to trigger a function only once that period expires.

A vanilla Javascript approach looks like the following:

# HTML
<input id="input-field" onKeyUp="inputChanged()" type="text"/>
# JS
const inputField = document.getElementById("input-field");
function getResult() {
console.log("Input event detected.");
console.log(inputField.value);
// Do something with the input
}
const debounce = {
timerId: 0,
timeout: 1000,

submit: function (func) {
this.cancel();

this.timerId = setTimeout(() => {
func.apply(this);
}, this.timeout);
},

cancel: function() {
clearTimeout(this.timerId);
}
}
function inputChanged() {
debounce.submit(getResult);
}

Key Points

Dissecting the above snippet, the key points are:

  • Submitting a task sets a timer with a threshold time
  • Each subsequent event will cancel the timer
  • Thus prolonging the execution ie. delaying the read of events
  • The task is executed only once the threshold time has elapsed
  • Ie. it wasn’t prolonged by another event

The above snippet replicates what’s earlier said and illustrated on the Stream of Events diagram.

I strongly suggest reading more about debounce. There are many posts across the web explaining the issue in more detail, under different circumstances, and displaying different use-cases.

Trey Huffine explains this problem very well, with a neat approach to solving it in his Debounce in Javascript Medium story. Check it out.

If you are a fan of lodash, you might want to check out their docs as they have a debounce method implemented for use.

RxJS obviously has an implementation as well, but even if you personally are not using it, make sure you check out the docs, as it illustrates the concept really well. The following RxJS documentation inspired my take on drawing an illustrated diagram — the Stream of Events.

This story was about a concept that not many know of, but is general to different Software Engineering fields, languages, platforms, and frameworks.

If you’d like to know more about debounce, if you have any suggestions, or if you find a typo in the story and code snippets, please do feel free to write a comment!

Hopefully, you enjoyed it and found it interesting to read. If so, check out my profile and hit follow and subscribe to my newsletter, to tune in for new stories that I’ll be writing on Software Engineering concepts.

Thank you for reading! 🎉

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store