Implement Throttle
Interview Question: "Implement a throttle function that ensures fn is called at most once every interval milliseconds."
The Core Implementation
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}Production Version (Leading + Trailing)
The basic version only fires on the leading edge. A proper throttle fires on both leading and trailing edges:
function throttle(fn, interval) {
let lastTime = 0;
let timeoutId;
return function(...args) {
const now = Date.now();
const remaining = interval - (now - lastTime);
if (remaining <= 0) {
clearTimeout(timeoutId);
timeoutId = undefined;
lastTime = now;
fn.apply(this, args);
} else if (!timeoutId) {
timeoutId = setTimeout(() => {
lastTime = Date.now();
timeoutId = undefined;
fn.apply(this, args);
}, remaining);
}
};
}Why the trailing call matters: If the user scrolls and stops mid-interval, the last scroll position should still be processed.
Debounce vs Throttle â The Key Distinction
Input: ââxâxâxâxâxâxâxâââââââââ
Debounce: ââââââââââââââââââxâââââââ (fires once after silence)
Throttle: ââxâââââxâââââxâââââxââââ (fires at regular intervals)- Debounce: "Wait until they stop." â Search input, resize handler, auto-save
- Throttle: "Run at most N times per second." â Scroll tracking, mouse move, rate limiting
Common Mistakes
- Only implementing leading edge (missing trailing call)
- Using
setIntervalinstead ofsetTimeout(wrong mental model â throttle is per-call, not periodic) - Not preserving
thisand arguments