Medium JavaScript

Debounce vs Throttle in JavaScript: Control How Often Code Runs

Scroll, resize, and keystroke events fire dozens of times a second. Learn how debounce and throttle rate-limit them — and which one to reach for.

09 Jul, 2026 2 min 18 Views 2 Code blocks
Diagram
flowchart TD E["Rapid events<br/>keystroke, scroll, resize"] --> Q{"Which strategy?"} Q -->|Debounce| D1["Reset timer on<br/>every event"] D1 --> D2{"Quiet for<br/>delay ms?"} D2 -->|"No, more events"| D1 D2 -->|Yes| D3["Run once with<br/>the final value"] Q -->|Throttle| T1["Run now,<br/>start cooldown"] T1 --> T2["Ignore events<br/>during interval"] T2 --> T3{"Interval<br/>elapsed?"} T3 -->|No| T2 T3 -->|Yes| T1

Some events fire dozens of times a second — scroll, resize, mousemove, and every keystroke in a search box. If you run expensive work (an API call, a layout recalculation) on each one, you flood the main thread and the network. Debounce and throttle are two rate-limiting techniques that fix this — and they solve different problems.

Debounce: wait until the storm passes

A debounced function delays running until events stop coming for a set period. Every new event resets the timer, so it fires only once, after things go quiet.

Use it when you only care about the final state:

  • Search-as-you-type (call the API after the user stops typing)
  • Validating a field after the user finishes editing
  • Autosaving a draft once edits pause
function debounce(fn, delay = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const search = debounce((q) => fetch(`/api/search?q=${q}`), 300);
input.addEventListener('input', (e) => search(e.target.value));

Type "laravel" quickly and only one request goes out — 300 ms after the last keystroke.

Throttle: run at a steady rate

A throttled function runs at most once per interval, no matter how many events fire. It doesn't wait for quiet — it guarantees a regular cadence.

Use it when you want periodic updates during a continuous stream:

  • Updating a scroll-progress bar
  • Recomputing layout on resize
  • Rate-limiting rapid clicks
function throttle(fn, interval = 200) {
  let last = 0;
  return (...args) => {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}

const onScroll = throttle(() => updateProgressBar(), 200);
window.addEventListener('scroll', onScroll);

Scroll for 3 seconds and the bar updates ~15 times (every 200 ms), not on all 100+ scroll events.

Which one do you need?

Ask yourself: do you want the result after activity settles, or during it?

  • "After it settles" → debounce (search, autosave, validation).
  • "At a steady rate while it happens" → throttle (scroll, resize, drag).

In production, reach for a battle-tested implementation like lodash.debounce / lodash.throttle — they handle leading/trailing edges, cancellation, and maxWait for you.