Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to throttle a function

Short answer

Throttle lets the function run at most once per interval and ignores the calls in between:

function throttle(fn, ms) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= ms) {
      last = now;
      fn(...args);
    }
  };
}
 
const log = throttle((n) => console.log('call', n), 100);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
 
for (let i = 1; i <= 10; i++) {
  log(i);
  await sleep(30);
}

Output:

call 1
call 4
call 8

On scroll

The scroll event fires dozens of times per second. Throttle keeps the handler cheap:

window.addEventListener('scroll', throttle(() => {
  backToTopButton.hidden = window.scrollY < 500;
}, 200));

Debounce or throttle

Debounce waits for a pause and calls once at the end: good for search fields. Throttle calls regularly while events keep coming: good for scrolling, resizing and mouse movement.

input.addEventListener('input', debounce(search, 300));
window.addEventListener('resize', throttle(updateLayout, 200));

Common mistake

This simple throttle drops the last call. If the final state matters, for example the final scroll position, add a trailing call with a timer:

function throttle(fn, ms) {
  let last = 0;
  let timer;
  return (...args) => {
    const now = Date.now();
    clearTimeout(timer);
    if (now - last >= ms) {
      last = now;
      fn(...args);
    } else {
      timer = setTimeout(() => {
        last = Date.now();
        fn(...args);
      }, ms - (now - last));
    }
  };
}
 
const log = throttle((n) => console.log('call', n), 100);
log(1);
log(2);
log(3);

Output:

call 1
call 3

Functions used in this recipe

  • setTimeout — Runs a function once after a delay.
  • Date.now — Returns the current time as a number of milliseconds.

Thanks for reading!

Donate to the site — we work hard for you.