DG LogoDeepak G.
Back to Series

Debounce technique

4 min readJavaScript Series

Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often, making them more efficient. It's an essential concept in JavaScript for improving performance, especially when dealing with user inputs or browser events.

Let's visualize why debouncing is so important with a classic example: Searching.

The Problem: Searching on Every Keystroke

Imagine you are building a search bar. If you want to do searching in a list of 100 items, triggering a search function on every single keystroke might seem fine. The browser can handle it easily.

But what if the list has thousands of items? Or worse, what if every keystroke triggers an API request to a database? Firing a heavy operation on every single keystroke will quickly degrade your app's performance, cause lag, or even overload your server with unnecessary requests. In situations like this, debouncing is a mandatory concept.

Interactive Playground

Type in the input box below to simulate searching through thousands of items. Notice how the "Raw Search" triggers instantly on every keystroke, but the "Debounced Search" (which would trigger the expensive operation) waits exactly 800ms after you stop typing before executing.

Raw Search Triggers
0
API calls / heavy operations
Query: ...
Debounced Search Triggers
0
API calls / heavy operations
Query: ...
"A debounced function ensures that your code is only triggered once a user has stopped an action, rather than firing continuously during the action."

The Implementation in Vanilla JS

The debounce function forces a function to wait a certain amount of time before running. If the function is called again before the time is up, the timer resets. Here is how you can write a robust debounce function in pure Vanilla JavaScript:

function debounce(func, delay) {
  // This variable holds the reference to the setTimeout
  let timeoutId;

  // The debounced function that will be returned and used
  return function (...args) {
    // If there is an existing timeout, clear it to reset the clock
    if (timeoutId) {
      clearTimeout(timeoutId);
    }

    // Set a new timeout to execute the function after the delay
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

Now, let's apply our debounce function to a search input. We'll set the delay to 300 milliseconds.

const searchInput = document.getElementById('searchInput');

function performSearch(query) {
  console.log(`Searching for: "${query}"... (Heavy operation executed!)`);
  // Data processing or API call goes here
}

// 1. Wrap the search function in debounce
const debouncedSearch = debounce(performSearch, 300);

// 2. Use the debounced function in the event listener
searchInput.addEventListener('input', (event) => {
  debouncedSearch(event.target.value);
});

Now, when the user types quickly, the event listener still fires on every keypress, but it only resets the timer. The performSearch function will only execute 300ms after the user stops typing. This turns dozens of heavy operations into just 1, preserving your app's performance!

Other Real-World Use Cases

While search inputs are the most common use case, the debounce technique is universally applicable to any event that fires rapidly and repeatedly. By wrapping expensive operations in a debounce function, we can save massive amounts of processing power and bandwidth. Here are two other common scenarios:

1. Redrawing on Window Resize

When a user resizes their browser window, the resize event can fire dozens of times per second. If your application recalculates complex grid layouts, redraws D3.js charts, or executes heavy animations on every single pixel change, the browser will freeze and stutter.

By debouncing the resize event handler (e.g., waiting 200ms), the browser will hold off on the expensive recalculations until the user has finished dragging the window to its new size.

2. Interactive Map Panning

Imagine dragging around an interactive map to find nearby restaurants. Every micro-movement of your mouse changes the coordinate bounds of the map. If your app fetched new restaurants from the database on every single drag event, you would instantly exhaust your API quota and overload your servers.

By debouncing the map panning event (e.g., waiting 400ms), the app waits until the user releases the mouse or pauses their dragging before sending the final coordinates to the server to fetch the data.