API Request Patterns: Cancellation, Deduplication & Race Conditions
Advanced API client patterns solve race conditions, redundant bandwidth consumption, and stale responses using AbortController, promise memoization/deduplication, request pooling, and sequence tokenization.
Why it matters in interviews
Visual & Interactive Explanation
Autocomplete Race Condition: Without vs With Cancellation
Sequence & Data Exchange Flow
Types 'apple'
Dispatch request #1 (slow, 800ms)
Types 'banana' (200ms later)
controller.abort() on request #1
Dispatch request #2 (fast, 150ms)
Request #2 resolves 'banana' results
Render 'banana' dropdown (Correct!)
Code Examples & Implementation
import { useState, useEffect, useRef } from "react";
export function useSearch(query: string) {
const [results, setResults] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
if (!query.trim()) {
setResults([]);
return;
}
// 1. Abort any previous pending request
if (abortRef.current) {
abortRef.current.abort();
}
// 2. Create a new controller for the current query
const controller = new AbortController();
abortRef.current = controller;
setLoading(true);
fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
})
.then((res) => {
if (!res.ok) throw new Error("Search failed");
return res.json();
})
.then((data) => {
setResults(data);
setLoading(false);
})
.catch((err) => {
// Ignore AbortError — it is intentional!
if (err.name !== "AbortError") {
console.error("Search error:", err);
setLoading(false);
}
});
// Cleanup on unmount
return () => {
controller.abort();
};
}, [query]);
return { results, loading };
}export function pLimit(concurrency: number) {
const queue: Array<() => void> = [];
let activeCount = 0;
const next = () => {
activeCount--;
if (queue.length > 0) {
const run = queue.shift();
run?.();
}
};
return function runTask<T>(fn: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
const execute = () => {
activeCount++;
fn()
.then(resolve)
.catch(reject)
.finally(next);
};
if (activeCount < concurrency) {
execute();
} else {
queue.push(execute);
}
});
};
}
// Usage: Fetch 100 images with max 4 parallel connections
const limit = pLimit(4);
const promises = urls.map((url) => limit(() => fetch(url).then((r) => r.blob())));
const blobs = await Promise.all(promises);Interview-Ready Answers
Prevent race conditions by aborting prior in-flight requests via AbortController or discarding responses with lower sequence IDs; eliminate duplicate network traffic by caching active in-flight promises.
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
const controller = new AbortController();
const signal = controller.signal;
signal.addEventListener("abort", () => console.log("signal aborted"));
console.log("before abort");
controller.abort();
console.log("after abort");
console.log("is aborted:", signal.aborted);let latestId = 0;
function handleFetch(query: string) {
const reqId = ++latestId;
setTimeout(() => {
if (reqId === latestId) {
console.log("Applied:", query);
} else {
console.log("Discarded stale:", query);
}
}, query === "slow" ? 200 : 50);
}
handleFetch("slow");
handleFetch("fast");
// After 250ms, what is printed?Common Mistakes & Anti-Patterns
- Catching errors without checking `err.name === 'AbortError'`, causing unwanted error toasts on fast typing
- Not aborting in-flight requests in `useEffect` cleanup functions, causing state update memory warnings on unmounted components
- Failing to deduplicate simultaneous GET requests across sibling components
- Spawning hundreds of unthrottled concurrent requests, saturating the browser network thread pool
Real-World Architectural Scenario
A user rapidly clicks through 10 filter tabs in 3 seconds. The server takes 1.5 seconds per tab query. The UI ends up displaying tab #3's data even though tab #10 is highlighted. How do you guarantee the correct tab data is shown?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: