Skip to content

Debounce vs Throttle: Differences, Use Cases, and Implementations

Interview-ready guide to debounce vs throttle: how they work, when to use each, and clean JavaScript implementations (with leading/trailing options).

Rajesh DhimanFebruary 08, 2026~16 min
On this page

Debounce and throttle are common interview questions because they show whether you understand:

  • performance in the browser
  • event handling
  • timing control

They’re also extremely practical.


30‑second interview answer

Debounce delays execution until events stop firing for X ms (great for search input). Throttle runs at most once every X ms (great for scroll/resize). Debounce reduces calls by waiting for a pause; throttle reduces calls by enforcing a max rate.

Key points

  • Debounce = “wait for quiet”.
  • Throttle = “limit the rate”.
  • Decide based on whether you want the final value or continuous updates.

1) The difference (one-liner)

  • Debounce: “Only run after the event stops firing for X ms.”
  • Throttle: “Run at most once every X ms.”

2) When to use Debounce

Use debounce when you want the final result after the user pauses:

  • search input (typeahead)
  • resizing a window (final layout calc)
  • autosave after typing stops

Example

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

3) When to use Throttle

Use throttle when you want updates during continuous activity, but not too frequently:

  • scroll events (infinite scroll)
  • mousemove (dragging)
  • analytics pings

Example

window.addEventListener('scroll', throttle(() => {
  trackScroll();
}, 200));

4) Debounce implementation (simple)

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

Interview notes

  • we clear the previous timer
  • only the last call survives
  • this and args are preserved via apply

5) Throttle implementation (simple)

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

This is a "leading" throttle (runs immediately on first trigger).


6) More complete versions (leading/trailing)

Interviewers sometimes ask for options.

Debounce with leading option

export function debounceAdvanced(fn, delay = 300, { leading = false } = {}) {
  let timer;
  let invoked = false;
 
  return function (...args) {
    if (leading && !invoked) {
      fn.apply(this, args);
      invoked = true;
    }
 
    clearTimeout(timer);
    timer = setTimeout(() => {
      invoked = false;
      if (!leading) fn.apply(this, args);
    }, delay);
  };
}

Throttle with trailing call

export function throttleAdvanced(fn, interval = 200) {
  let lastTime = 0;
  let timer;
  let lastArgs;
  let lastThis;
 
  return function (...args) {
    const now = Date.now();
    lastArgs = args;
    lastThis = this;
 
    const remaining = interval - (now - lastTime);
 
    if (remaining <= 0) {
      clearTimeout(timer);
      timer = undefined;
      lastTime = now;
      fn.apply(lastThis, lastArgs);
      return;
    }
 
    // schedule a trailing call
    if (!timer) {
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = undefined;
        fn.apply(lastThis, lastArgs);
      }, remaining);
    }
  };
}

7) Production-grade Debounce: Adding .cancel() and .flush()

In real-world frontends (and Senior/Staff interviews), debounced functions must support cleanup on component unmount and immediate execution when submitting forms.

export function debounceWithControls(fn, delay = 300) {
  let timerId = null;
  let lastArgs = null;
  let lastThis = null;
 
  function debounced(...args) {
    lastArgs = args;
    lastThis = this;
    clearTimeout(timerId);
 
    timerId = setTimeout(() => {
      fn.apply(lastThis, lastArgs);
      timerId = null;
      lastArgs = null;
      lastThis = null;
    }, delay);
  }
 
  // Cancel any scheduled execution (crucial for React useEffect cleanup)
  debounced.cancel = function () {
    if (timerId !== null) {
      clearTimeout(timerId);
      timerId = null;
      lastArgs = null;
      lastThis = null;
    }
  };
 
  // Immediately execute the pending call without waiting for timer
  debounced.flush = function () {
    if (timerId !== null) {
      clearTimeout(timerId);
      fn.apply(lastThis, lastArgs);
      timerId = null;
      lastArgs = null;
      lastThis = null;
    }
  };
 
  return debounced;
}

React usage with cleanup

useEffect(() => {
  const debouncedSearch = debounceWithControls((query) => {
    fetchResults(query);
  }, 300);
 
  // Cancel pending timer if user unmounts before 300ms expires
  return () => debouncedSearch.cancel();
}, []);

8) Throttle via requestAnimationFrame (RAF Throttle)

For visual DOM operations (scroll positions, cursor trailers, resize calculations), JavaScript timers (setTimeout) do not sync with the browser monitor refresh rate (60Hz / 120Hz). Throttling with requestAnimationFrame coordinates execution directly with the browser render pipeline, preventing layout thrashing and dropped frames.

export function throttleRAF(fn) {
  let queued = false;
  let lastArgs = null;
 
  return function (...args) {
    lastArgs = args;
    if (queued) return;
 
    queued = true;
    requestAnimationFrame(() => {
      queued = false;
      fn.apply(this, lastArgs);
    });
  };
}

Interview note: requestAnimationFrame automatically pauses when the browser tab is in the background, conserving battery and CPU cycles.


9) Real Interview Puzzle: Predict the Timing Tick

Question: Consider the following calls to a debounced and throttled log function with an interval of 100ms. At what timestamps do the executions actually occur, and with what values?

const logDebounce = debounce((v) => console.log('Debounce:', v), 100);
const logThrottle = throttle((v) => console.log('Throttle:', v), 100);
 
// Timeline:
// t = 0ms:   call(1)
// t = 50ms:  call(2)
// t = 160ms: call(3)

Step-by-Step Execution Trace

Standard Trailing Debounce (delay = 100ms)

  • t = 0ms: call(1) received. Timer scheduled for t = 100ms.
  • t = 50ms: call(2) received. Previous timer cancelled! New timer scheduled for t = 50 + 100 = 150ms.
  • t = 150ms: Timer fires. Output: Debounce: 2.
  • t = 160ms: call(3) received. Timer scheduled for t = 160 + 100 = 260ms.
  • t = 260ms: Timer fires. Output: Debounce: 3.
  • Final outputs: Debounce: 2 at 150ms, Debounce: 3 at 260ms. call(1) was completely debounced away.

Standard Leading Throttle (interval = 100ms)

  • t = 0ms: call(1) received. Since 0 - 0 >= 100 (or lastTime == 0), runs immediately. Output: Throttle: 1. lastTime = 0ms. Next eligible run is at t >= 100ms.
  • t = 50ms: call(2) received. Elapsed time 50 - 0 = 50ms < 100ms. Call dropped (simple leading throttle).
  • t = 160ms: call(3) received. Elapsed time 160 - 0 = 160ms >= 100ms. Runs immediately. Output: Throttle: 3.
  • Final outputs: Throttle: 1 at 0ms, Throttle: 3 at 160ms. call(2) was suppressed.

10) Common interview Q&A

Q: Is debounce or throttle better for search input?

  • Debounce. You typically want the final value after the user pauses typing.

Q: What about a scroll or window resize handler?

  • Throttle. You want periodic updates while scrolling to provide smooth UI feedback. If visual updates only, prefer throttleRAF.

Q: Can you debounce a promise-returning function?

  • Yes, but you should handle potential race conditions. If an earlier asynchronous request finishes after a later one, use AbortController to abort the obsolete request.

Summary checklist

  • I can define debounce (wait for quiet) vs throttle (rate limiting).
  • I can choose between them based on whether intermediate values matter.
  • I can implement standard debounce and throttle from scratch in an interview.
  • I know how to add .cancel() and .flush() methods for lifecycle safety.
  • I can use requestAnimationFrame throttle for screen-synchronized rendering.

Authoritative References & Standards


Written by Rajesh Dhiman at Eunix Tech for the Frontend Interview preparation track.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback
Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.