CodePark/ questions / Client–server contract
START PRACTICINGPRACTICE
Frontend question · Frontend system design › Client–server contract

How do you handle request deduplication, cancellation and race conditions on the client? Give a concrete example.

useEffect(() => {
  const ctrl = new AbortController();
  fetch(`/api/search?q=${q}`, { signal: ctrl.signal })
    .then((r) => r.json())
    .then(setResults)
    .catch((e) => { if (e.name !== "AbortError") setError(e); });
  return () => ctrl.abort();
}, [q]);

Cancel or ignore old requests, share in-flight requests, and make mutations idempotent.

Race conditions: a search box is the classic example. The request for "re" can finish after the request for "react". It then overwrites the newer results. There are two fixes:

  • Cancel the previous request with AbortController when the input changes.
  • Ignore any response that is not for the latest query.

Cancellation also saves bandwidth and server work when a user navigates away. Query libraries pass an abort signal to the fetch function. They cancel when the key changes or the component unmounts.

Deduplication: a header, a sidebar and a page may all need the current user when they mount. They should share one in-flight request. A cache keyed by the request returns the same promise to every caller, then serves the cached result. TanStack Query and SWR do this. A simple map from key to promise also works.

On the server side of the contract, mutations should be idempotent or carry an idempotency key. Idempotent means repeating the request has no extra effect. Then a retry after a timeout cannot create duplicate orders.

  • Responses can arrive out of order and overwrite newer data.
  • Use AbortController to cancel when the key changes or the component unmounts.
  • Deduplicate by sharing one in-flight promise per key.
  • Use idempotency keys so retried mutations do not create duplicates.