Q35 of 40 · JavaScript
How do you implement debounce and throttle, and when do you use each?
Short answer
Short answer: Debounce delays execution until a period of inactivity — the function runs after the last call if no new call arrives within the delay. Throttle limits execution to once per interval regardless of how many times it's called. Debounce for search input; throttle for scroll/resize handlers.
Detail
Both patterns limit how often a function executes in response to high-frequency events.
Debounce: Resets a timer on every call. The function executes only after the last call's delay expires without another call. Use for: search-as-you-type (wait until typing stops), window resize (recalculate after resize finishes), auto-save.
Throttle: Executes at most once per interval. Intermediate calls during the interval are dropped (or queued for the next window). Use for: scroll event handlers, rate-limiting API calls from button spam, game loop input.
Implementation details:
- Both use closures over a timer ID or last-called timestamp.
- Leading-edge vs trailing-edge: debounce can fire immediately (leading) then ignore subsequent calls, or fire after inactivity (trailing, the default).
- Lodash
_.debounceand_.throttlehandle edge cases:maxWait, cancellation, flushing.
In test automation: Debounce and throttle in the tested UI require extra care in Playwright — you may need to wait for the debounce timeout before asserting the result. Playwright's page.waitForResponse or explicit page.waitForTimeout(delay) after triggering input handles this.
// EXAMPLE
// Debounce — trailing edge
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const search = debounce(query => fetchResults(query), 300);
input.addEventListener("input", e => search(e.target.value));
// Throttle — trailing interval
function throttle(fn, interval) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
window.addEventListener("scroll", throttle(() => updatePosition(), 100));
// Playwright — wait for debounce to settle
await page.fill("#search", "playwright");
await page.waitForTimeout(350); // wait past debounce delay
await expect(page.locator(".results")).toBeVisible();