How to create a debounce function in JavaScript

Event handlers for search boxes, resize listeners, and other fast-changing UI signals can fire many times before the user has finished the action. A debounce function keeps that noisy input from calling expensive code until activity has been quiet for a chosen delay.

The wrapper keeps one timer in a closure. Every call cancels the previous timer with clearTimeout() and starts a new one with setTimeout(), so only the latest arguments reach the callback when the timer expires.

Use this pattern for delayed work such as search requests, validation, draft saving, or layout recalculation after a burst of input. A terminal smoke test can run in Node.js to prove the timer behavior, and the same helper can wrap browser handlers registered with addEventListener().

Steps to create a debounce function in JavaScript:

  1. Create a module file for the debounce helper.
    debounce-demo.mjs
    function debounce(callback, delay = 300) {
      let timeoutId;
     
      return function debounced(...args) {
        clearTimeout(timeoutId);
     
        timeoutId = setTimeout(() => {
          callback.apply(this, args);
        }, delay);
      };
    }

    The timeoutId variable belongs to one returned wrapper. Create separate debounced functions when separate controls should keep separate timers.

  2. Add a short timer-based smoke test below the helper.
    const wait = (milliseconds) =>
      new Promise((resolve) => setTimeout(resolve, milliseconds));
     
    const runSearch = debounce((query) => {
      console.log(`Searching for: ${query}`);
    }, 250);
     
    runSearch("j");
    runSearch("ja");
    runSearch("jav");
    runSearch("java");
     
    console.log("Queued 4 rapid calls");
     
    await wait(350);

    clearTimeout() is safe on the first call because there is no matching timer to cancel yet. After that, each new call cancels the previous pending callback.

  3. Run the module with Node.js to verify only the final value reaches the callback.
    $ node debounce-demo.mjs
    Queued 4 rapid calls
    Searching for: java
  4. Wrap a browser input handler with the same helper when using it on a search field.
    const searchInput = document.querySelector("#search");
    const updateResults = debounce((event) => {
      fetchResults(event.target.value);
    }, 300);
     
    searchInput.addEventListener("input", updateResults);

    The input event fires for user edits in fields such as input and textarea, so the debounce delay prevents one search request per keystroke.

  5. Remove the temporary demo module when finished.
    $ rm debounce-demo.mjs