Before this lesson

Understand input events, setTimeout, closures and array filtering.

What you will be able to do

Reduce repeated work during typing and distinguish debounce from request cancellation.

Understand the mechanism

Debounce resets a timer and runs after a pause; throttle limits execution frequency over an interval. Search often benefits from waiting for a pause, while dragging requires ongoing feedback.

Pass the input string to delayed work rather than retaining an event or reading uncertain DOM state later. Avoid searching intermediate IME composition text; isComposing and compositionend help update after confirmation.

This example filters a local array, where debounce is mainly instructional. With network requests, clearing a timer only prevents work that has not started. Already-issued requests still need cancellation and stale-result protection.

Runnable example

<label for="query">Find a course</label><input id="query" type="search">
<p id="result" role="status"></p>
<script>
const courses = ['HTML semantics', 'CSS Grid', 'JavaScript promises'];
const input = document.querySelector('#query');
const result = document.querySelector('#result');
function debounce(fn, delay) {
  let timer;
  return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
}
const update = debounce(query => {
  const found = courses.filter(title => title.toLowerCase().includes(query.trim().toLowerCase()));
  result.textContent = found.length ? found.join(' · ') : 'No matching courses';
}, 250);
input.addEventListener('input', event => { if (!event.isComposing) update(input.value); });
input.addEventListener('compositionend', () => update(input.value));
update('');
</script>

Trace and verify

Type css quickly and pause to see CSS Grid. Clear for all results and enter an unknown term for the empty state. Test an IME and count calls inside the delayed callback to compare debounced behavior.

Common mistakes and boundaries

250 milliseconds is not universal. Tune for interaction needs. Debouncing essential validation or persistence without a flush strategy can lose the final operation when the page closes.

Practice challenge

Add Search now, canceling pending work and immediately processing the latest query without a later duplicate.

Reference solution and reasoning

Extract search, expose cancel on the debounce wrapper, and have the button cancel then call search(input.value) directly.

Further reading