Debounce vs Throttle: Explaining Rate-Shaping in a Frontend Interview
"Implement debounce" is one of the most common frontend interview prompts, and the follow-up is almost always "now how is that different from throttle?" Here is a compact way to reason about both.
What problem they solve
Some events fire far more often than your handler needs: keystrokes in a search box, scroll, resize, mousemove. Running an expensive handler (a network request, a layout calculation) on every event wastes work and can make the page janky. Both techniques reduce how often the handler runs, but they choose different moments.
Debounce: wait for quiet
A debounced function runs only after the events have stopped for N milliseconds. Every new call resets the timer.
function debounce(fn, wait) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
Use it when only the final value matters: search-as-you-type, autosave after editing stops, validating a field once the user pauses.
Throttle: at most once per window
A throttled function runs at most once every N milliseconds, no matter how many calls arrive.
function throttle(fn, wait) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= wait) {
last = now;
fn.apply(this, args);
}
};
}
Use it when you need steady updates during continuous activity: scroll position tracking, infinite-scroll checks, drag handlers.
Follow-up questions to expect
1. Leading vs trailing edge. Should the first call fire immediately (leading), the last call fire after the wait (trailing), or both? Lodash exposes both options; be ready to add a leading flag.
2. Preserving this and arguments. Using fn.apply(this, args) inside a regular function keeps the caller’s context. Arrow functions in the wrapper would lose it.
3. Cancellation. Add a cancel() method that clears the pending timer, useful when a component unmounts.
4. Trailing call in throttle. The simple version above drops the last event in a burst; a better version schedules one trailing call so the final state is never lost.
5. React usage. Creating the debounced function inside render makes a new timer each render. Wrap it in useMemo or useRef so it survives re-renders.
A one-line way to remember
Debounce answers "has the user finished?"; throttle answers "has enough time passed?".
Practicing out loud helps
These questions reward clear explanation more than memorized code. Talking through edge cases with a timer, or rehearsing with a mock interview tool such as Linkjob AI (https://www.linkjob.ai/), makes the follow-ups feel routine. Disclosure: I work on Linkjob AI.