Debounce vs throttle: what is the difference?

HTML, CSS & JS 2 min read
Short answer

Debounce runs the function once the activity has stopped for N ms — right for search input. Throttle runs it at most once every N ms during activity — right for scroll and resize.

#Debounce

javascript
function debounce(fn, wait = 300) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), wait);
  };
}

input.addEventListener('input', debounce((e) => search(e.target.value), 300));

Typing "python" fires six input events but only one search — 300ms after the last keystroke. Without it you send six requests and may render them out of order.

#Throttle

javascript
function throttle(fn, limit = 100) {
  let waiting = false;
  return (...args) => {
    if (waiting) return;
    fn(...args);
    waiting = true;
    setTimeout(() => { waiting = false; }, limit);
  };
}

window.addEventListener('scroll', throttle(updateProgress, 100), { passive: true });

Scrolling fires dozens of events per second. Throttling caps it at ten, which is plenty for a progress bar and far cheaper.

#Which to use

SituationChoose
Search-as-you-typeDebounce
Autosave a draftDebounce
Validating on inputDebounce
Scroll positionThrottle
Window resizeThrottle (or debounce the final layout)
Infinite scroll triggerThrottle

#For scroll, prefer the platform

javascript
let ticking = false;
window.addEventListener('scroll', () => {
  if (ticking) return;
  ticking = true;
  requestAnimationFrame(() => { update(); ticking = false; });
}, { passive: true });

requestAnimationFrame runs exactly once per frame, in sync with painting — better than any fixed interval. And { passive: true } tells the browser you will not call preventDefault, so scrolling is not blocked waiting for your handler.

Better still, for "is this element visible" questions, use IntersectionObserver and skip scroll handlers entirely.