mediumFrontend EngineerRide-sharing
Explain React's useEffect cleanup function and common race condition scenarios in data fetching
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At a ride-sharing company, the interviewer presented:
> "Our search page fetches results as the user types. Sometimes old results appear after newer ones. Here's the code:"
> "Our search page fetches results as the user types. Sometimes old results appear after newer ones. Here's the code:"
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
fetch(/api/search?q=${query})
.then(r => r.json())
.then(data => setResults(data));
}, [query]);
return <ResultList results={results} />;
}
> "Type 'abc' then quickly change to 'xyz'. Sometimes 'abc' results show after 'xyz' results. Why?"Suggested Solution
The Race Condition
Typing 'abc' → fetch('abc') starts → takes 800ms
Typing 'xyz' → fetch('xyz') starts → takes 200ms
→ 'xyz' returns first → setResults(xyz data) ✅
→ 'abc' returns later → setResults(abc data) ❌ OVERWRITES!
The older, slower request overwrites the newer results.Fix 1: Cleanup with AbortController
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
const controller = new AbortController();
fetch(/api/search?q=${query}, { signal: controller.signal })
.then(r => r.json())
.then(data => setResults(data))
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort(); // Cancel previous request
}, [query]);
return <ResultList results={results} />;
}
Fix 2: Boolean Flag
useEffect(() => {
let cancelled = false;
fetch(/api/search?q=${query})
.then(r => r.json())
.then(data => {
if (!cancelled) setResults(data);
});
return () => { cancelled = true; };
}, [query]);
Fix 3: use() hook with Suspense (React 19+)
function SearchResults({ query }) {
const results = use(fetch(/api/search?q=${query}));
return <ResultList results={results} />;
}
// React automatically handles cancellation via Suspense boundaries
Other Common useEffect Cleanup Scenarios
removeEventListener()clearInterval() / clearTimeout()ws.close()unsubscribe().destroy())