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.

reader loved this