useDebounce vs useThrottle: Optimizing Search Autocomplete and Scroll Events

Chris built an 'Employee Search' feature for the company intranet.
"Wouldn't it be super convenient if it searched in real-time as you type?"
However, just one hour after deployment, the backend developer came running.
"Chris! The DB CPU hit 100%! Is someone launching a DDoS attack?"
It turned out the culprit was Chris's code. While a user typed "Chris", five API requests (C, Ch, Chr, Chri, Chris) were fired at 0.1-second intervals. With just 100 users, the server started screaming.
Processing frequent events like keyboard input, scrolling, or resizing as-is leads to a performance disaster. Today, we will compare Debounce and Throttle, the two core techniques to control this, and implement them as custom hooks.
1. Debounce: "Just do the last one!"
Debounce is a technique that ensures only the last function (or the very first) is executed among a series of calls.
Implementation: useDebounce Hook
In React, we implement this by resetting a timer whenever the value changes.
// useDebounce.ts
import { useState, useEffect } from 'react';
// value: The value to watch (e.g., search keyword)
// delay: Time to wait (ms)
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// Update value after the delay time has passed.
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// What if value changes again before the delay passes?
// Cancel (clear) the previous timer and start a new one. (This is the key!)
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}Usage
// SearchBar.tsx
function SearchBar() {
const [keyword, setKeyword] = useState('');
// The value only changes 0.5 seconds after the user stops typing.
const debouncedKeyword = useDebounce(keyword, 500);
// useEffect runs only when debouncedKeyword changes (i.e., after 500ms).
useEffect(() => {
if (debouncedKeyword) {
console.log('API Request:', debouncedKeyword);
}
}, [debouncedKeyword]);
return <input onChange={(e) => setKeyword(e.target.value)} />;
}2. Throttle: "Keep a steady pace!"
Throttle restricts events so they fire only once per specified interval (e.g., once every 1 second).
Implementation: useThrottle Hook
Throttle needs to use useRef to remember the "last executed time."
// useThrottle.ts
import { useRef, useCallback } from 'react';
export function useThrottle<T extends (...args: any[]) => void>(
callback: T,
delay: number
) {
const lastRun = useRef(Date.now());
return useCallback(
(...args: Parameters<T>) => {
const timeElapsed = Date.now() - lastRun.current;
// Allow execution only if the specified time (delay) has passed
if (timeElapsed >= delay) {
callback(...args);
lastRun.current = Date.now(); // Update time
}
},
[callback, delay]
);
}Usage
// ScrollComponent.tsx
function ScrollComponent() {
const handleScroll = () => {
console.log('Calculating scroll position...');
};
// Even if scrolled rapidly, it executes only once every 0.2 seconds.
const throttledScroll = useThrottle(handleScroll, 200);
return (
<div onScroll={throttledScroll} style={{ height: '300px', overflow: 'scroll' }}>
{/* Content */}
</div>
);
}3. Debounce vs Throttle: Which Should I Use?
The choice depends clearly on the situation.
| Situation | Recommended | Reason |
| Search Input | Debounce | Searching while typing is meaningless. Request should be sent after input is finished. |
| Infinite Scroll | Throttle | We need to check the position during the scroll to know if we hit the bottom. |
| Window Resize | Throttle | The layout should change in real-time while resizing to look natural. |
| Prevent Double Click | Debounce | Accept only the last click (or Leading Edge), or use Throttle to space out clicks. |
Key Takeaways
We've reduced the load on the server and browser through event optimization. But re-calculating heavy logic or recreating functions inside a component on every render is also a waste of performance.
It's time to properly use React's memoization trio (useMemo, useCallback, React.memo) to prevent this waste.
Continuing in: “useMemo, useCallback, React.memo: Memoization Is Not Free”
🔗 References
More in Optimization & Security
No other posts in this part.