What is the difference between debouncing and throttling?

JavaScriptGoogleAmazonMetaUber

The short answer

Both debouncing and throttling are techniques to limit how often a function runs. Debouncing waits until the user stops doing something and then runs the function once. Throttling runs the function at regular intervals no matter how many times the event fires. Use debouncing for search inputs, use throttling for scroll and resize events.

The problem they solve

Some events in the browser fire very frequently. A scroll event can fire 30+ times per second. A keyup event fires on every single keystroke. If you attach a heavy function (like an API call or DOM calculation) to these events, it will run way too often and cause performance problems.

// Without debounce or throttle
searchInput.addEventListener('input', function (event) {
// This fires on EVERY keystroke
// If user types "react hooks", this makes 11 API calls
fetchSearchResults(event.target.value);
});

If the user types "react hooks", that is 11 keystrokes and 11 API calls. Most of those calls are wasted — we only care about the final result. This is where debouncing and throttling help.

Debouncing — wait until they stop

Debouncing says: "I will only run after the user has stopped for a certain amount of time."

Imagine you are in an elevator. The door starts closing, but someone walks in. The door resets and starts closing again. Another person walks in. The door resets again. The door only actually closes when no one has walked in for a few seconds. That is debouncing.

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

Let me walk through how this works:

  • Every time the returned function is called, it clears the previous timer using clearTimeout
  • Then it sets a new timer with setTimeout
  • If the function is called again before the timer expires, the old timer gets cleared and a new one starts
  • The original function only runs when the timer actually completes — meaning the user has stopped for the full delay duration

Here is how you would use it:

const handleSearch = debounce(function (query) {
fetchSearchResults(query);
}, 300);
searchInput.addEventListener('input', function (event) {
handleSearch(event.target.value);
});

Now if the user types "react hooks" quickly, the function only runs once — 300ms after they stop typing. Instead of 11 API calls, we make 1.

When to use debouncing:

  • Search input (wait until user finishes typing)
  • Window resize (recalculate layout after resizing stops)
  • Form validation (validate after user stops changing a field)
  • Auto-save (save after user stops editing)

Throttling — run at regular intervals

Throttling says: "I will run at most once every X milliseconds, no matter how many times you call me."

Think of it like a faucet — no matter how much you turn the handle, water only flows at a fixed rate. The function runs on a regular schedule regardless of how frequently the event fires.

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

Here is what is happening:

  • We keep track of the last time the function ran using lastTime
  • Every time the returned function is called, we check if enough time has passed since the last execution
  • If enough time has passed, we run the function and update lastTime
  • If not enough time has passed, we skip the call

Here is how you would use it:

const handleScroll = throttle(function () {
updateScrollProgress();
}, 100);
window.addEventListener('scroll', handleScroll);

Even if the scroll event fires 30 times per second, updateScrollProgress only runs once every 100ms (10 times per second). The user still gets smooth visual feedback without overloading the browser.

When to use throttling:

  • Scroll events (infinite scroll, scroll progress)
  • Mouse move events (drag and drop, tooltips)
  • API rate limiting (sending analytics events)
  • Game loops (limiting frame rate)

The key difference

Here is the simplest way to remember it:

  • Debounce: Runs once after activity stops. "Wait until they are done."
  • Throttle: Runs periodically during activity. "Run, but not too often."

If you are typing in a search bar:

  • Debounce with 300ms: The function runs 300ms after you stop typing. If you keep typing, it keeps waiting.
  • Throttle with 300ms: The function runs every 300ms while you are typing. You will see intermediate results.

Common Pitfalls

A common mistake candidates make is using debounce when they should use throttle, and the other way around.

The rule is simple — if the user needs feedback while they are doing the action (scrolling, dragging, resizing), use throttle. If the user only cares about the final result (finished typing, stopped resizing), use debounce.

Using debounce on a scroll handler means the user sees nothing until they stop scrolling, which feels broken. Using throttle on a search input means you are making unnecessary API calls for every partial word.

Debouncing and throttling in React

In React, you need to be careful about how you create debounced or throttled functions. If you create them inside the component body, a new function gets created on every render, which breaks the debouncing.

// Wrong — new debounced function on every render
function SearchBar() {
const [query, setQuery] = useState('');
// This creates a NEW debounced function every render
// The timer resets on every render, debounce never works
const debouncedSearch = debounce((value) => {
fetchResults(value);
}, 300);
const handleChange = (event) => {
setQuery(event.target.value);
debouncedSearch(event.target.value);
};
return <input value={query} onChange={handleChange} />;
}
// Correct — stable reference across renders
function SearchBar() {
const [query, setQuery] = useState('');
const debouncedSearch = useRef(
debounce((value) => {
fetchResults(value);
}, 300)
).current;
const handleChange = (event) => {
setQuery(event.target.value);
debouncedSearch(event.target.value);
};
return <input value={query} onChange={handleChange} />;
}

By using useRef, the debounced function is created once and keeps the same reference across renders.

Interview Tip

If an interviewer asks you to implement debounce or throttle from scratch, start by explaining the concept in simple words, then write the implementation. Make sure you can explain what clearTimeout does in debounce and why you track lastTime in throttle. If they ask a follow-up about using it in React, mention the useRef pattern — it shows you understand how React rendering interacts with closures and function identity.

Why interviewers ask this

Debouncing and throttling come up in interviews because they test multiple things at once — your understanding of closures (both implementations use them), your knowledge of browser performance, and your ability to make practical engineering decisions about when to use which technique. Interviewers also like that both can be implemented in under 10 lines of code, so they can ask you to code it on the spot and then discuss the tradeoffs. Being able to explain both clearly and know when to pick one over the other shows you write code that works well in production, not just code that works.