Back to Blogs
Deep technical guide
JavaScript
Debounce
Throttle
Performance
Frontend
Interview Questions

Debounce vs Throttle in JavaScript: Complete Guide With Search, Scroll, and API Examples

Learn the difference between debounce and throttle in JavaScript with clear timelines, reusable implementations, search input, scroll, resize, autocomplete, API, React, and interview examples.

JS Interview Prep Editorial Team

Author

September 1, 2026

Published

6 min read

Reading time

0 views

Views

Debounce vs Throttle in JavaScript: Complete Guide With Search, Scroll, and API Examples
SEO-friendly JavaScript learning article

Debounce vs Throttle in JavaScript: Complete Guide With Search, Scroll, and API Examples

Debounce and throttle are high-value JavaScript topics because they appear in autocomplete boxes, search filters, resize handlers, scrolling, drag interactions, analytics, autosave, and rate-limited APIs. They are also a common frontend interview question because the correct choice depends on product behavior, not memorising one utility function.

Both techniques reduce the number of times a function runs. Debounce waits for a quiet period before running. Throttle limits execution to at most once per time window. The difference sounds small, but it changes the experience a user receives and the load your backend receives.

Debounce versus throttle at a glance

  • Debounce: run after events stop for a chosen delay. Best when only the final action matters.
  • Throttle: run at most once during a chosen interval. Best when continuous feedback is useful but every event is too expensive.
  • Neither: use the original handler when each event is meaningful or the work is already cheap.

Think about typing into a search field. The user may trigger ten input events while entering a phrase, but a request after they pause is usually enough: debounce. Think about scrolling a long page. You may want to update a progress indicator regularly during the scroll, but not hundreds of times a second: throttle.

How debounce works

A debounced function clears its existing timer whenever a new event arrives. It schedules a new timer, and the underlying function runs only if no further event arrives before the delay ends.

function debounce(callback, delay = 300) {
  let timerId;

  return function debounced(...args) {
    const context = this;
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      callback.apply(context, args);
    }, delay);
  };
}

The closure stores timerId between calls. Each new call cancels the earlier scheduled work. apply preserves both this and the latest arguments, which matters when debouncing a method or event handler.

Debounced search and autocomplete

const searchInput = document.querySelector('#search');

const searchProducts = debounce(async (event) => {
  const query = event.target.value.trim();
  if (query.length < 2) return;

  const response = await fetch('/api/search?q=' + encodeURIComponent(query));
  const products = await response.json();
  renderResults(products);
}, 300);

searchInput.addEventListener('input', searchProducts);

Debouncing reduces unnecessary requests, but it does not solve response ordering. A slow response for an old query can arrive after a fast response for the newest query. For production autocomplete, cancel the previous request with AbortController or ignore responses that do not match the latest request id.

let activeController;

const searchProducts = debounce(async (event) => {
  activeController?.abort();
  activeController = new AbortController();

  const response = await fetch('/api/search?q=' + encodeURIComponent(event.target.value), {
    signal: activeController.signal,
  });

  renderResults(await response.json());
}, 300);

How throttle works

A throttled function allows one execution, then prevents more executions until the interval has passed. This simple leading-edge implementation is useful for visual updates and telemetry where an immediate response is desired.

function throttle(callback, interval = 200) {
  let lastRun = 0;

  return function throttled(...args) {
    const now = Date.now();
    if (now - lastRun < interval) return;

    lastRun = now;
    callback.apply(this, args);
  };
}

This version runs on the leading edge only. Some products also need a trailing call after activity ends. Be explicit about that behaviour rather than assuming every throttle helper behaves the same way.

Throttled scroll and resize work

const updateReadingProgress = throttle(() => {
  const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
  const progress = maxScroll === 0 ? 0 : window.scrollY / maxScroll;
  document.querySelector('#progress').style.transform = 'scaleX(' + progress + ')';
}, 100);

window.addEventListener('scroll', updateReadingProgress, { passive: true });

For browser painting work such as animation or visual scroll updates, requestAnimationFrame can be a better fit than a time-based throttle because it aligns work with the browser render cycle. For non-visual work such as analytics, a throttle interval may be clearer.

Leading, trailing, cancel, and flush behaviour

Robust debounce and throttle utilities often expose options. Leading means run immediately at the start of a burst. Trailing means run once after the burst ends. cancel drops scheduled work, while flush runs it immediately. These details matter for autosave, form submission, route changes, and component cleanup.

  • Search suggestions: usually trailing debounce.
  • Save button: often no debounce; a single explicit action matters.
  • Autosave: trailing debounce with cancel on document close or deliberate flush before navigation.
  • Scroll position or resize feedback: throttle or requestAnimationFrame.
  • Rate-limited telemetry: throttle with a final trailing send if the final value matters.

Common bugs and how to avoid them

  • Creating a new debounced function on every render or every event. Create it once for the lifecycle that needs to share its timer.
  • Forgetting to preserve this and arguments in a reusable helper.
  • Using debounce as the only backend protection. APIs still need server-side rate limits and validation.
  • Ignoring stale async responses after a debounced request.
  • Never cancelling timers or listeners during component cleanup.
  • Throttling keyboard input when the UI must immediately reflect every keystroke.

Debounce and throttle in React

React components need stable utility instances. If a component recreates a debounced function on every render, it also recreates the timer state and defeats debouncing. Keep the instance stable for the intended lifecycle and cancel it during cleanup.

function ProductSearch() {
  const [query, setQuery] = useState('');

  const debouncedSearch = useMemo(
    () => debounce((value) => fetchProducts(value), 300),
    []
  );

  useEffect(() => () => debouncedSearch.cancel?.(), [debouncedSearch]);

  function handleChange(event) {
    const value = event.target.value;
    setQuery(value);
    debouncedSearch(value);
  }

  return <input value={query} onChange={handleChange} />;
}

If fetchProducts depends on changing state or props, design the dependencies deliberately. A stable callback, a ref for latest values, or a library helper can be appropriate depending on the component. Always test that the latest query is requested and unmounting does not update an unmounted component.

Interview-ready answer

A strong answer: “Debounce delays execution until events stop for a defined time, so I use it for search input, autosave, and validation where only the final value matters. Throttle limits execution to one call per interval, so I use it for continuous events such as scroll, resize, and telemetry. I also consider leading and trailing behaviour, cancel timers during cleanup, and do not rely on client-side throttling for backend security.”

Frequently asked questions

Should I debounce every API request?

No. Debounce only event bursts where delaying work improves the experience or reduces waste. A deliberate button click or checkout submission should usually run immediately with normal loading and duplicate-submission protection.

Is requestAnimationFrame the same as throttle?

No. requestAnimationFrame schedules visual work before a browser paint. It is often ideal for animation-related updates, while throttle is a general time-window limit.

Does debounce protect my backend from abuse?

No. It improves client behaviour only. Your backend still needs authentication, validation, rate limiting, and resource controls.

Continue practising with JS Interview Prep

Use the JavaScript interview questions guide for topic-wise revision, then apply the ideas in the interactive practice area. Reading an answer is useful; explaining it and writing it under time pressure builds interview confidence.

Official references

Buy Me A Coffee