Learn and Teach Coding
JS

Debounced Search in JavaScript

Implement a debounced search input with vanilla JavaScript.

JavaScriptIntermediatedebounceperformancesearchevents

Source code

function debounce(fn, delay = 300) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

Walkthrough

Debouncing delays running a function until the user stops triggering it. For a search box that means we only hit the API once typing pauses — not on every keystroke. The two source files are on the left.

1. The debounce closure

debounce returns a new function that wraps fn. A single timer lives in the closure; each call clears the pending timer and starts a fresh one, so only the last call within delay actually runs.

function debounce(fn, delay = 300) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

fn.apply(this, args) forwards both the this context and every argument, so the debounced function behaves just like the original — only later.

2. The search function

The work we want to throttle: skip empty queries, call the API, and paint results. By itself it would fire on every keystroke.

async function search(query) {
  if (!query) { results.innerHTML = ""; return; }
  const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
  const items = await res.json();
  results.innerHTML = items.map((i) => `<li>${i.name}</li>`).join("");
}

3. Wiring it to the input

We hand the input listener a debounced version of search. Type quickly and only the final pause triggers a request.

input.addEventListener(
  "input",
  debounce((e) => search(e.target.value), 300),
);

That's the whole pattern — a reusable debounce plus one line to wire it up. The complete files are in the panel.