Hey everyone, I’m tightening up a scroll handler for a small canvas viewer, and I’m trying to keep it responsive without flooding state updates. My current throttle mostly works, but on fast trackpad bursts it sometimes fires an extra call at the edge, which makes the UI feel jumpy.
function throttle(fn, wait) {
let last = 0;
let trailingId = null;
return (...args) => {
const now = Date.now();
const remaining = wait - (now - last);
if (remaining <= 0) {
last = now;
fn(...args);
} else if (!trailingId) {
trailingId = setTimeout(() => {
last = Date.now();
trailingId = null;
fn(...args);
}, remaining);
}
};
}
What is the cleanest way to make this throttle guarantee one leading call and at most one trailing call per burst without double-firing near the boundary?
Hari 
@Hari yup, the extra fire is because the old trailing timeout is still alive when a later event lands right on the boundary and takes the immediate path. Clear that pending timeout in the remaining <= 0 branch, and keep the latest args around so the one trailing call uses fresh event data.
js
function throttle(fn, wait) {
let last = 0;
let trailingId = null;
let trailingArgs = null;
return (...args) => {
const now = Date.now();
const remaining = wait - (now - last);
trailingArgs = args;
if (remaining <= 0) {
if (trailingId) {
clearTimeout(trailingId);
trailingId = null;
}
last = now;
fn(...args);
} else if (!trailingId) {
trailingId = setTimeout(() => {
last = Date.now();
trailingId = null;
fn(...trailingArgs);
}, remaining);
}
};
}
That gives you one leading call and at most one trailing call per burst, without the near-boundary double hit. If you want slightly cleaner timing on trackpads, swapping Date.now() for performance.now() is a nice small upgrade.
Ellen