Glossary

Throttle

Throttle is a technique that caps how often a function can run, for example at most once every 100 milliseconds, however many times the event behind it fires. Developers use it on high-frequency browser events such as scroll, resize and mousemove so a handler does not run on every event and cause jank. The ECMAScript standard, ECMA-262, defines no built-in throttle function, so you write one or import one, such as the throttle function in the lodash library.

How it works

A throttled wrapper remembers when the wrapped function last ran. When a call arrives, it compares the current time with that timestamp:

  • Leading edge: if at least wait milliseconds have passed, run the function now and record the time.
  • Inside the window: if the window is still open, do not run. Remember the latest arguments.
  • Trailing edge: when the window closes, optionally run once with the latest arguments, so the final state is never lost.

Lodash defaults to running on both the leading and trailing edges. It runs the trailing call only if the throttled function was called more than once during the wait. Its throttle is built on its debounce function with maxWait set equal to wait, which is why the two share options.

The example below fires 12 events, one every 10 ms, through a 50 ms throttle. Run with Node.js 24, it printed the output below on every run, and lodash 4.18.1's throttle ran the same four calls. Timers are not exact, so a busy machine can shift which events run.

function throttle(fn, wait) {
  let last = 0, timer = null, lastArgs;
  return function (...args) {
    const now = Date.now();
    lastArgs = args;
    if (now - last >= wait) {
      clearTimeout(timer); timer = null;
      last = now;
      fn(...args);
    } else if (!timer) {
      timer = setTimeout(() => {
        timer = null; last = Date.now(); fn(...lastArgs);
      }, wait - (now - last));
    }
  };
}

const run = throttle((n) => console.log(`event ${n} ran`), 50);
let n = 0;
const id = setInterval(() => {
  run(++n);
  if (n === 12) clearInterval(id);
}, 10);
// event 1 ran
// event 4 ran
// event 8 ran
// event 12 ran

What is the difference between throttle and debounce?

Throttle runs a function at a steady maximum rate while events keep arriving, and debounce waits until events stop. With the same 12 events and a 50 ms wait, lodash's throttle ran the handler for events 1, 4, 8 and 12, while its debounce ran it once, for event 12. Use throttle for continuous feedback such as scroll position or drag, and debounce for a final value such as a search box after typing stops.

Common pitfalls

  • Throttling scroll with requestAnimationFrame: MDN calls this useless for the scroll event, because animation frame callbacks fire at the same rate as scroll event handlers. Measure the interval yourself with a timer or timestamp.
  • Dropping the last event: a leading-only throttle ignores everything after the first call in a window. If the final value matters, such as the resting scroll position, keep the trailing call.
  • Creating the throttle inside the handler: each call then builds a new wrapper with its own timestamp, so nothing is ever throttled. Create it once, outside the handler, and pass that same function to addEventListener.
  • Stale arguments: the trailing call must use the latest arguments, not the first ones. Store them on every call, as lastArgs does above.
  • Cleanup on teardown: a pending trailing timer can fire after a component unmounts. Lodash's throttled function has a cancel method for this, and a hand-written one needs to expose the same.
  • Using it for correctness: throttle only smooths event rates in one page. It does not enforce a server-side rate limit, which must live on the server.

Related terms

  • Debounce — waits for a pause in events instead of limiting the rate while they continue.
  • DOM — the source of high-frequency events such as scroll, resize and mousemove.

See also

  • Term: Debounce — the companion technique, with a side-by-side contrast above.