Debounce vs throttle in React
The short answer
Debouncing waits until activity stops before running your function — use it when only the final value matters, such as a search input or autosave. Throttling runs your function at most once per interval while activity continues — use it when you need regular updates during the activity, such as a scroll progress bar or a drag handler. Debounce answers "what did they settle on?"; throttle answers "where are they now?".
Both techniques limit how often a function runs. They are not interchangeable, and picking the wrong one produces a UI that feels either laggy or broken.
Debounce: wait for the pause
A debounced function resets its timer every time it is called. It only actually runs once the calls stop for the full delay. For a search box, that means one request when the user finishes typing rather than one per keystroke.
Pick the delay deliberately
150–250 ms feels instant and still cuts most requests. 300–500 ms is the sweet spot for network calls. Above 800 ms the UI starts to feel unresponsive, because the average person pauses that long mid-word.The same hook covers autosave, which is the other place debouncing shines — you want one write after the user stops typing, not one per character.
Throttle: a steady drip
A throttled function runs immediately, then refuses to run again until the interval has elapsed. Scroll events fire dozens of times a second; a throttled handler turns that into a manageable stream while still updating continuously.
Often you need neither
For scroll-triggered UI,IntersectionObserver beats a throttled scroll handler outright — the browser does the work off the main thread and tells you only when something actually crosses the viewport. Use useOnScreen or useIntersectionObserver instead of throttling.Why the naive React implementation breaks
The instinct is to wrap the handler in lodash.debounce inside the component. It does not work, and the reason is worth understanding.
Each render produces a brand-new debounced function with a brand-new internal timer, so no call ever survives long enough to fire. Wrapping it in useCallback fixes the identity but introduces a stale closure — the memoised function captures the state from the render that created it.
Debouncing the value rather than the callback sidesteps both problems entirely. There is no function identity to preserve and no closure to go stale.
Key takeaway
Debounce the value, not the callback. const debounced = useDebounce(value, 300) has no identity problem, no stale closure, and no cleanup to forget.
Choosing, in one question
- 1
Do you need updates while the activity is happening?
Yes → throttle. A progress bar, a follow-the-cursor tooltip and a drag preview all need continuous feedback.
- 2
Do you only care about the final value?
Yes → debounce. Search queries, autosave, form validation and resize-driven layout recalculation only need the settled value.
- 3
Is it about an element entering or leaving the viewport?
Then neither — use IntersectionObserver. It is more accurate and cheaper than any listener you can throttle.
Hooks referenced here
Is there a useThrottle hook in @danixsoft/hooks?
Not currently. Most throttling needs in React are better served by IntersectionObserver (useOnScreen, useIntersectionObserver) or by a ref-based timestamp inside a useEventListener handler, as shown above. Debouncing a value covers the remaining cases.
What debounce delay should I use for a search input?
Start at 300 ms. Lower it toward 150 ms if your backend is fast and the result set is small; raise it toward 500 ms if each request is expensive. Above 800 ms the input begins to feel unresponsive.
Does useDebounce cancel the pending update on unmount?
Yes. It clears its timeout in the effect cleanup, so no state update is attempted after the component has unmounted.
Can I debounce and throttle the same value?
You can, but it usually signals that two different consumers want different things. Keep the raw value for the throttled consumer and derive a debounced copy for the other — they can coexist from one source of truth.
Keep reading
The React Hooks Cheat Sheet
A complete reference to every built-in React hook and the 44 custom hooks in @danixsoft/hooks — what each one does, when to reach for it, and the mistake people make with it.
Writing custom hooks that survive production
Naming, return shapes, dependency arrays, cleanup, testing and TypeScript patterns for custom React hooks that other people have to maintain.