Debounce vs Throttle: Two Event Control Techniques Every Developer Needs
Events like scroll, window resize, and input can fire handlers hundreds of times per second. Left unchecked, the CPU gets hammered and the UI stutters. Debounce and Throttle solve this — but they work in completely different ways.

Trung Vũ Hoàng
Author
1. Debounce — "Wait until it's over, then act"
Debounce forces a function to wait a specified amount of time after the last trigger before executing. If the event fires again during the wait, the countdown resets to 0.
Think of an automatic elevator: it doesn’t close the doors the moment you step in. It waits 5 seconds — if someone else steps in at second 3, it waits another 5 seconds. It only closes when no one steps in for 5 consecutive seconds.
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Only call the API after the user stops typing for 500ms
const searchHandler = debounce(() => fetchResults(query), 500);2. Throttle — "Keep a steady pace"
Throttle ensures a function executes at most once within a fixed time window — no matter how many times the event fires during that window.
Like a scheduled bus: it departs every 15 minutes. Whether 1 or 100 people are waiting, it still leaves on the set cadence.
function throttle(func, limit) {
let lastRan;
return function(...args) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
};
}
// Check scroll position at most once per second
const scrollHandler = throttle(() => checkScrollPosition(), 1000);3. Side-by-Side Comparison
Execution timing
Debounce: After the event pauses (trailing edge).
Throttle: At a steady cadence while the event is happening.
Call frequency
Debounce: Might be called only once even if the user keeps interacting for several seconds.
Throttle: Calls multiple times at a steady cadence based on the total duration and the interval.
UX goal
Debounce: Accurate results after the interaction is complete.
Throttle: Continuous feedback, but within controlled limits.
4. When Should You Use Each?
Choose Debounce when:
Search autocomplete: Wait for the user to stop typing before calling the API — avoiding dozens of unnecessary requests.
Real-time form validation: Check whether an email already exists after the user finishes typing, not on every keystroke.
Window resize: Recalculate layout only after the user finishes resizing the window.
Choose Throttle when:
Infinite scroll: Check whether the user is near the bottom — every 100–200ms is sufficient.
Mouse tracking / Canvas drawing: Track mouse coordinates for drawing or effects with continuous but limited updates.
Prevent Submit button spam: Users may click repeatedly — only process at the defined cadence.
Using the wrong technique = bad UX: Using Debounce for mouse tracking makes brush strokes on a canvas appear only when the user stops moving — the app looks completely frozen.
5. Notes When Using in React
When using Debounce/Throttle in a React component, wrap the function with useCallback to avoid recreating it on every render — that would reset the timer and break the logic:
const debouncedSearch = useCallback(
debounce((query) => fetchResults(query), 500),
[] // Create the debounced function only once
);In production, prefer _.debounce and _.throttle from Lodash — they handle tricky edge cases (leading/trailing edges, cancellation, etc.) that homegrown implementations often miss.
Wrap-up
Debounce and Throttle are fundamentals in every web developer’s toolkit. Quick rules of thumb:
Debounce → "Act only when the user stops."
Throttle → "Act at a steady rate even if the user doesn’t stop."
Audit your application — which events are wasting resources unnecessarily? That’s exactly where these two techniques shine.
Frequently Asked Questions
Bài viết liên quan

Zustand Async: 5 Effective Ways to Handle Async in React
Every real-world React app needs to communicate with async APIs. If handled poorly, it’s easy to run into issues like frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an extremely simple syntax—define async actions directly in the store, without complex middleware like Redux Thunk or Saga.

Advanced Promises: all, allSettled, race, any — When to Use Which?
If you only use sequential async/await, your app is wasting performance potential — each request has to wait for the previous one to finish before it can start. Promise’s static methods (all, allSettled, race, any) let you orchestrate multiple async tasks in parallel using the strategy that fits each problem.

Zustand Async: 5 Effective Ways to Handle Async Operations in React
Every real-world React app has to communicate with asynchronous APIs. If managed poorly, your app can run into issues like a frozen UI, race conditions, memory leaks, or showing stale data. Zustand solves this with an incredibly simple syntax — define async actions directly in the store, without complex middleware like Redux Thunk or Saga.