# Why does my debounced input handler sometimes use the wrong value?

**URL:** <https://forum.kirupa.com/t/why-does-my-debounced-input-handler-sometimes-use-the-wrong-value/681785>\
**Category:** web dev\
**Created:** [May 28, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-debounced-input-handler-sometimes-use-the-wrong-value/681785 "2026-05-28T07:00:14Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![VaultBoy](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/vaultboy/32/31832_2.png) [@VaultBoy](https://forum.kirupa.com/u/VaultBoy)\
**Post date:** [May 28, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-debounced-input-handler-sometimes-use-the-wrong-value/681785/1 "2026-05-28T07:00:14Z")

</div>

Yo folks, I’m wiring up a search box in a vanilla JS page and trying to debounce the input event so I don’t spam the API, but sometimes it fetches using an older query even though the UI shows the latest text.

```js
const input = document.querySelector('#q');
let timer;

input.addEventListener('input', (e) => {
  const q = e.target.value;
  clearTimeout(timer);
  timer = setTimeout(async () => {
    const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
    render(await res.json());
  }, 250);
});

```

What’s the cleanest way to guarantee only the newest keystroke wins (including slow network responses) without making the code super tangled?

---

<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:** [May 29, 2026, 7:40am UTC](https://forum.kirupa.com/t/why-does-my-debounced-input-handler-sometimes-use-the-wrong-value/681785/2 "2026-05-29T07:40:31Z")

</div>

The stale value isn’t the debounce, it’s the response race. An older fetch can finish after the newer one and still call `render()`.

Abort the previous request before starting the next one, and keep a tiny sequence check so only the latest response is allowed through:

```auto
const input = document.querySelector('#q');
let timer;
let controller;
let seq = 0;

input.addEventListener('input', (e) => {
  const q = e.target.value;
  clearTimeout(timer);

  timer = setTimeout(async () => {
    const mySeq = ++seq;

    controller?.abort();
    controller = new AbortController();

    try {
      const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
        signal: controller.signal
      });
      const data = await res.json();
      if (mySeq === seq) render(data);
    } catch (err) {
      if (err.name !== 'AbortError') throw err;
    }
  }, 250);
});

```

That keeps the UI from being overwritten by some slow response from three keystrokes ago.
