Debounced Search in JavaScript
Implement a debounced search input with vanilla JavaScript.
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.