Lesson 18 of 80
Two closure-based patterns for taming a noisy stream of events — one waits for quiet, the other enforces a steady maximum rate.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 2
A user types 5 characters quickly into a debounced (300ms) search box, then stops. How many times does the debounced function actually run?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Both wrap a function to control how often it actually runs, but they solve different problems:
scroll/resize handlers: you want regular updates, not silence followed by one final burst.function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer); // cancel the pending call
timer = setTimeout(() => fn(...args), delay);
};
}
function throttle(fn, interval) {
let lastRun = 0;
return function (...args) {
const now = Date.now();
if (now - lastRun >= interval) {
lastRun = now;
fn(...args); // run immediately, then go quiet
}
};
}
Both are closures over per-instance state (a timer id; a last-run timestamp) — the same mechanism as memoize's cache, just applied to timing instead of results.
Five raw calls arrive close together — a user typing fast, or a scroll event firing repeatedly.
clearTimeout is the whole trick. Every new call cancels the previous pending timer and starts a fresh one — only the last call in a burst ever actually survives to fire."Implement debounce" (and its sibling "implement throttle") is one of the most common live-coding exercises in frontend interviews specifically because it checks closures, timers, and precise control flow all in about eight lines.