Hiprup

How do you implement throttle from scratch?

Throttle limits a function to run at most once per time interval, however often it's called.

The approach:

  1. Track whether you're in a "cooldown" period (a flag or last-run timestamp).

  2. On a call, if outside the cooldown, run the function and start the cooldown.

  3. Ignore calls during the cooldown; reset when it ends.

Versus debounce: throttle runs on a steady schedule, while debounce waits for silence.

function throttle(fn, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// Usage: scroll handler fires at most every 100ms
const onScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
}, 100);

window.addEventListener('scroll', onScroll);

// Throttle with trailing call (ensures last call executes)
function throttleTrailing(fn, interval) {
  let lastTime = 0;
  let timer = null;
  return function(...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);
    if (remaining <= 0) {
      clearTimeout(timer);
      lastTime = now;
      fn.apply(this, args);
    } else if (!timer) {
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  };
}

Throttle checks elapsed time since last execution. If >= interval, execute immediately and update lastTime.

If not, the call is dropped. The trailing variant schedules a final execution for the remaining time — ensuring the last event is not lost (important for scroll position accuracy).

Simpler than debounce: just check elapsed time. Know the difference from debounce: throttle = regular rate, debounce = wait for pause.

The trailing variant (ensures last call fires) is the advanced follow-up. Show usage with scroll events.

How do you implement throttle from scratch? | Hiprup