What are "debounce" and "throttle" for requests?
The problem they solve
If a user types quickly in a search field:
R
Re
Rea
Reac
Reactand on every keystroke you send a request:
fetch(`/api/search?q=${query}`);then the server gets 5-10 requests per second, even though only the last one is needed. This:
- overloads the server,
- creates unnecessary
setStatecalls and re-renders, - can cause a data "race" (an old response arriving after a newer one).
This is where debounce and throttle help - ways to control how often functions are called (for example, network requests).
1. Debounce
Debounce delays running a function until the user stops calling it for a given amount of time.
That is:
- every new call resets the timer;
- the function fires only after a pause.
Example
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}Usage in React:
const debouncedSearch = useMemo(
() => debounce((value) => fetch(`/api/search?q=${value}`), 500),
[]
);
<input onChange={(e) => debouncedSearch(e.target.value)} />;The user types:
r → re → rea → reac → react-> fetch() runs once, 500 ms after the last keystroke.
Great for:
- search requests;
- autocomplete;
- filtering as you type;
- checking whether a name/email is unique while typing.
2. Throttle
Throttle lets a function run no more often than once per given interval.
That is:
- the first call happens immediately;
- subsequent calls are ignored until the timer expires.
Example
function throttle(fn, delay) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn(...args);
}
};
}Usage in React:
const throttledScroll = useMemo(
() => throttle((event) => console.log(window.scrollY), 200),
[]
);
useEffect(() => {
window.addEventListener('scroll', throttledScroll);
return () => window.removeEventListener('scroll', throttledScroll);
}, [throttledScroll]);When scrolling fires 60 times a second -> the handler runs only every 200 ms (about 5 times a second).
Great for:
scroll,resize,mousemovehandlers;- updating position, element visibility;
- "infinite scroll".
Comparison
| Trait | Debounce | Throttle |
|---|---|---|
| When it runs | after a pause | at regular intervals |
| Behavior on frequent calls | waits for "silence" | fires periodically |
| Good for | search, text input | scroll, resize, drag-and-drop |
| Number of calls | minimal | capped |
| UX effect | "waits until the user finishes" | "limits how often updates happen" |
A combined example (search with debounce)
import { useState, useMemo, useEffect } from "react";
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
export function SearchBox() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const search = useMemo(
() => debounce(async (q) => {
const res = await fetch(`/api/search?q=${q}`);
setResults(await res.json());
}, 400),
[]
);
useEffect(() => {
if (query) search(query);
}, [query, search]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
</div>
);
}The user types - the request runs only after 400 ms of "silence".
Summary
| What it does | How it works | Where to use it |
|---|---|---|
| Debounce | waits until the user stops calling the function | search, filters, validation while typing |
| Throttle | runs no more often than every X ms | scroll, resize, drag, infinite-scroll |
| Both | protect against a "storm of calls" and reduce load | improve UX and performance |
In short:
Debounce - "run it once the user is done". Throttle - "run it no more than once every N ms".
Both are needed to avoid spamming the server and blocking the UI with unnecessary renders during frequent events.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.