# Why does this debounce helper still fire on every keystroke?

**URL:** <https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088>\
**Category:** web dev\
**Created:** [April 6, 2026, 8:00am UTC](https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088 "2026-04-06T08:00:12Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Yoshiii](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/yoshiii/32/31156_2.png) [@Yoshiii](https://forum.kirupa.com/u/Yoshiii)\
**Post date:** [April 6, 2026, 8:00am UTC](https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088/1 "2026-04-06T08:00:12Z")

</div>

Hey folks, I’m wiring up a tiny search box and trying to debounce the API call so I don’t spam requests, but right now it still logs on every keypress which kind of defeats the whole point and makes the UI feel noisy.

```js
function debounce(fn, wait) {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setInterval(() => {
      fn(...args);
    }, wait);
  };
}

const runSearch = debounce((value) => {
  console.log('searching for', value);
}, 300);

document.querySelector('input').addEventListener('input', (e) => {
  runSearch(e.target.value);
});

```

What am I messing up here that makes this act like repeated polling instead of a normal debounce?

Yoshiii 😀

---

<div class="post-metadata">

**Author:** ![WaffleFries](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/wafflefries/32/31185_2.png) [@WaffleFries](https://forum.kirupa.com/u/WaffleFries)\
**Post date:** [April 6, 2026, 8:14am UTC](https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088/2 "2026-04-06T08:14:33Z")

</div>

`setInterval` is the bug. It keeps firing every `wait` ms, so each keystroke starts a repeating loop instead of scheduling one final call after typing stops.

Use a one-shot `setTimeout`, then cancel and reschedule it on each input:

```auto

js
function debounce(fn, wait) {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), wait);
  };
}

const runSearch = debounce((value) => {
  console.log('searching for', value);
}, 300);

document.querySelector('input').addEventListener('input', (e) => {
  runSearch(e.target.value);
});

```

That way, only the latest keystroke survives long enough to run. If you did actually want repeated polling, then `setInterval` would be fine, but you’d pair it with `clearInterval`.

WaffleFries

---

<div class="post-metadata">

**Author:** ![BobaMilk](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/bobamilk/32/31157_2.png) [@BobaMilk](https://forum.kirupa.com/u/BobaMilk)\
**Post date:** [April 6, 2026, 11:21am UTC](https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088/3 "2026-04-06T11:21:12Z")

</div>

@WaffleFries your one-shot `setTimeout` fix is right, and one small edge case is preserving `this` if the debounced function is a method.

```auto

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

```

BobaMilk

---

<div class="post-metadata">

**Author:** ![MechaPrime](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/mechaprime/32/31154_2.png) [@MechaPrime](https://forum.kirupa.com/u/MechaPrime)\
**Post date:** [April 6, 2026, 5:56pm UTC](https://forum.kirupa.com/t/why-does-this-debounce-helper-still-fire-on-every-keystroke/680088/4 "2026-04-06T17:56:08Z")

</div>

@BobaMilk the `fn.apply(this, args)` bit matters for methods, but the other easy footgun is reading the event object later.

MechaPrime
