DEV Community

Cover image for Debounce vs Throttle — The Visual Guide Every Frontend Developer Needs
YT Low
YT Low

Posted on Originally published at onlywebpro.com

Debounce vs Throttle — The Visual Guide Every Frontend Developer Needs

Full guide with an interactive side-by-side demo, React hooks, and a complete decision table on
onlyWebPro.

The one-line difference:

  • Debounce — "wait until they've stopped, then act once"
  • Throttle — "act on a fixed schedule, no matter what"

Pick the wrong one and your UI either feels broken (debounce on scroll progress) or fires too many API calls (throttle on search input).


Debounce — built from scratch

Every call resets the timer. The function only runs once the events have been quiet for the full delay.

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Only calls the API after 300ms of typing silence
const handleSearch = debounce((query) => fetchResults(query), 300);
searchInput.addEventListener('input', (e) => handleSearch(e.target.value));
Enter fullscreen mode Exit fullscreen mode

Throttle — built from scratch

Checks elapsed time and skips calls that arrive too soon.

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

// Updates scroll progress at most every 100ms
const updateProgress = throttle(() => {
  const pct = window.scrollY / (document.body.scrollHeight - window.innerHeight);
  progressBar.style.width = (pct * 100) + '%';
}, 100);

window.addEventListener('scroll', updateProgress, { passive: true });
Enter fullscreen mode Exit fullscreen mode

Always add passive: true to scroll listeners that don't call preventDefault() — it's a free performance win.


The critical difference in output behaviour

Debounce Throttle
Output while events are active Zero — silent Regular — fires on schedule
Output after events stop One final call Nothing
Best for Search, validation, auto-save Scroll tracking, progress bars, analytics

This is why debounce is wrong for scroll-linked UI — it shows nothing while the user is actively scrolling.


When to use requestAnimationFrame instead

For anything directly changing visual properties (transform, opacity, parallax), skip throttle entirely. Use requestAnimationFrame — it syncs with the browser's actual paint cycle for smoother results.

let ticking = false;
window.addEventListener('scroll', () => {
  if (ticking) return;
  requestAnimationFrame(() => {
    el.style.transform = `translateY(${window.scrollY * 0.5}px)`;
    ticking = false;
  });
  ticking = true;
}, { passive: true });
Enter fullscreen mode Exit fullscreen mode

The three-way decision

  • Debounce → search inputs, form validation, auto-save, resize recalculation
  • Throttle → scroll progress, analytics, infinite scroll triggers, API rate-limiting
  • requestAnimationFrame → parallax, sticky header opacity, canvas animations

The full guide has a live interactive demo where you can click rapidly and watch raw events, debounced output, and throttled output on separate timelines — the clearest way to see how differently they behave.

👉 Read the full guide + interactive demo


Part 3 of the Frontend Performance Series on onlyWebPro.

Which do you reach for more often — debounce or throttle? And has rAF replaced throttle for you on scroll animations?

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.