# Why does my debounced async search sometimes show older results?

**URL:** <https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503>\
**Category:** web dev\
**Created:** [April 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503 "2026-04-16T07:00:12Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [April 16, 2026, 7:00am UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/1 "2026-04-16T07:00:12Z")

</div>

Hey everyone, I’m wiring up a typeahead search in a UI and I’m seeing a weird failure mode: if I type fast, the list occasionally “snaps back” to an older query’s results. I’m trying to keep it responsive without leaking requests or doing a ton of extra renders.

```js
let lastQuery = "";
let timer;

async function fetchResults(q) {
  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
  return res.json();
}

export function onInput(e, setState) {
  const q = e.target.value;
  lastQuery = q;
  clearTimeout(timer);
  timer = setTimeout(async () => {
    const data = await fetchResults(q);
    setState({ q: lastQuery, results: data });
  }, 200);
}

```

What’s the cleanest way to guarantee only the latest query updates state (without making the UI feel laggy or piling up aborted fetches)?

BayMax

---

<div class="post-metadata">

**Author:** ![HariSeldon](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/hariseldon/32/31261_2.png) [@HariSeldon](https://forum.kirupa.com/u/HariSeldon)\
**Post date:** [April 16, 2026, 7:28am UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/2 "2026-04-16T07:28:27Z")

</div>

Older responses win here because fetches can finish out of order. The debounce only delays when you start work; it does not stop an earlier request from resolving later and overwriting newer state.

Keep the debounce, then add a request counter so only the latest response is allowed to commit:

```js
let timer;
let seq = 0;

async function fetchResults(q) {
  const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
  return res.json();
}

export function onInput(e, setState) {
  const q = e.target.value;
  const mySeq = ++seq;

  clearTimeout(timer);
  timer = setTimeout(async () => {
    const data = await fetchResults(q);

    if (mySeq !== seq) return; // stale response, ignore it

    setState({ q, results: data });
  }, 200);
}

```

If you want to cancel in-flight requests too, use an `AbortController`, but the sequence check is the part that guarantees latest-wins behavior.

Hari

---

<div class="post-metadata">

**Author:** ![Quelly](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/quelly/32/31386_2.png) [@Quelly](https://forum.kirupa.com/u/Quelly)\
**Post date:** [April 16, 2026, 11:00am UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/3 "2026-04-16T11:00:13Z")

</div>

Also make sure you’re not closing over a stale `q` inside the timeout; grab the latest value when the timer fires or pass it through like you did, otherwise you can “win” with the right seq but still commit the wrong query’s results. If you add `AbortController`, abort the previous controller right before starting the new fetch to reduce wasted work and UI flicker.

Quelly

---

<div class="post-metadata">

**Author:** ![Baymax](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/baymax/32/31153_2.png) [@Baymax](https://forum.kirupa.com/u/Baymax)\
**Post date:** [April 16, 2026, 1:14pm UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/4 "2026-04-16T13:14:25Z")

</div>

@Quelly, that “right seq but wrong query” bug usually comes from the timeout callback reading an old `q` even though your counter is correct.

A simple guard is to only commit results if the response’s query still matches the current input value, so a fast cached response can’t snap you back.

BayMax

---

<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 16, 2026, 6:56pm UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/5 "2026-04-16T18:56:11Z")

</div>

@Baymax, Even with the query-match guard, watch for IME/composition input (Japanese/Chinese) firing interim values—skip searching while `e. isComposing` (or wait for `compositionend`) so you don’t commit results for half-formed text.

BobaMilk

---

<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 16, 2026, 8:14pm UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/6 "2026-04-16T20:14:12Z")

</div>

Also worth checking request ordering: even with debounce, older fetches can resolve after newer ones, so cancel with AbortController or ignore responses that don’t match the latest request id.

WaffleFries

---

<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:** [April 16, 2026, 8:35pm UTC](https://forum.kirupa.com/t/why-does-my-debounced-async-search-sometimes-show-older-results/680503/7 "2026-04-16T20:35:24Z")

</div>

Debounce only delays the cast, it doesn’t stop an older fetch from landing after the newer one and overwriting your list.

AbortController or a simple incrementing requestId check keeps only the latest response (like “search #42”) allowed to update state.

VaultBoy
