GitHub

Copyright © 2026 Sumit Paul

Design by Sumit Paul•LinkedIn

LinkedIn

Async race condition in search results

Avatar

Sumeet Kumar Paul

Frontend Engineer

2 min readSource code
Feb 10, 2026

Problem

Under rapid input changes and slow network conditions, multiple search requests can remain in flight at the same time. When responses resolve out of order, older results may overwrite newer state, causing the UI to display incorrect data.

The implementation assumes that the most recent request will resolve last. This assumption breaks under real-world network latency and asynchronous execution.

  • ●Multiple async requests in flight simultaneously.
  • ●Variable or throttled network latency.
  • ●Rapid user input before previous requests resolve.
  • ●State updates tied directly to promise resolution.
page.tsx
srcapppracticeasync-race-condition-in-search-resultspage.tsx
1useEffect(() => {
2  if (!query.trim()) return;
3
4  void (async () => {
5    try {
6      const data = await fetchResults(query);
7      console.log("commit results for:", query);
8      setResults(data);
9    } catch (error) {
10      console.error("search request failed", error);
11    }
12  })();
13}, [query]);

This implementation works in happy paths but does not guard against out-of-order async resolution. Any response that resolves later is allowed to update state, regardless of when it was initiated.

Solution

Each request is assigned an identity, and state updates are only allowed from the most recent request. Responses that resolve out of order are ignored instead of committing stale data.

page.tsx
srcapppracticeasync-race-condition-in-search-resultspage.tsx
1const requestIdRef = useRef(0);
2
3useEffect(() => {
4  if (!query.trim()) {
5    requestIdRef.current = 0;
6    return;
7  }
8
9  const requestId = ++requestIdRef.current;
10
11  void (async () => {
12    try {
13      const data = await fetchResults(query);
14      if (requestId === requestIdRef.current) {
15        console.log("commit results for:", query);
16        setResults(data);
17      } else {
18        console.log("ignored stale response for:", query);
19      }
20    } catch (error) {
21      console.error("search request failed", error);
22    }
23  })();
24}, [query]);

By guarding state updates with request identity, the UI becomes resilient to async timing differences and network variability.

This issue is not specific to search, debouncing, or React. It occurs whenever UI state is updated directly from asynchronous work without guarding against out-of-order resolution. Treating async responses as untrusted by default and committing state only when intent is still valid prevents an entire class of UI correctness bugs.

Keywords

async race condition, react state, frontend edge case, ui correctness, search

More Cases

2 min readFeb 20, 2026

State update after route transition

A component starts asynchronous work and then a route transition occurs before the work completes. When the async task resolves, it updates state that no longer matches the active route.

Avatar

Sumeet Kumar Paul

2 min readFeb 18, 2026

Optimistic UI rollback failure

An optimistic UI update immediately reflects a user action before the server confirms success. If the request fails or multiple toggles occur quickly, the UI can drift away from actual server state.

Avatar

Sumeet Kumar Paul

2 min readFeb 16, 2026

Double submit caused by UI state lag

A submit button is disabled only after state updates to 'loading'. Under rapid user interaction, multiple clicks can occur before the UI visually reflects the disabled state.

Avatar

Sumeet Kumar Paul

2 min readFeb 13, 2026

Stale closure in click outside listener

Event listeners defined inside effects capture state from the render during which they were created. If the listener is attached once and depends on state that later changes, it may continue operating on outdated values.

Avatar

Sumeet Kumar Paul