Skip to content

Debounce and Throttle

When to Use

Use for events that fire rapidly (scroll, resize, input, mousemove) where executing handler every time causes performance issues.

Decision

Situation Choose Why
Search input, form validation Debounce Execute only after user stops typing
Window resize handlers Debounce Execute only after resize completes
Scroll progress tracking Throttle Execute at controlled intervals
Visual updates, animations requestAnimationFrame Sync with browser refresh rate

Debounce: Execute handler only after events stop for specified time. Throttle: Execute handler at most once per specified interval.

Pattern

Debounce pattern (wait until events stop):

// Dependency: core/drupal.debounce
Drupal.behaviors.search = {
  attach(context) {
    once('search', '.search-input', context).forEach(function (input) {
      // Handler executes 300ms after user stops typing
      const debouncedSearch = Drupal.debounce(function (event) {
        performSearch(event.target.value);
      }, 300);

      input.addEventListener('input', debouncedSearch);
    });
  }
};

Throttle pattern (limit execution rate):

// Execute at most once per 100ms
let lastRun = 0;
window.addEventListener('scroll', function () {
  const now = Date.now();
  if (now - lastRun >= 100) {
    lastRun = now;
    handleScroll();
  }
});

requestAnimationFrame (visual updates):

// Better than throttle for visual changes
let ticking = false;
window.addEventListener('scroll', function () {
  if (!ticking) {
    requestAnimationFrame(function () {
      handleScroll();
      ticking = false;
    });
    ticking = true;
  }
});

Common Mistakes

  • Wrong: No debounce on input events → Right: Debounce search/validation
  • Why: Handler fires on every keystroke, performance penalty
  • Wrong: Scroll handler without throttle → Right: Throttle or use requestAnimationFrame
  • Why: Executes hundreds of times during scroll, freezes UI
  • Wrong: Using setTimeout instead of debounce → Right: Use Drupal.debounce
  • Why: setTimeout doesn't cancel previous calls, accumulates
  • Wrong: requestAnimationFrame for non-visual logic → Right: Use throttle instead
  • Why: Tied to refresh rate, not appropriate for all throttling

See Also