Here's a debounce function with a usage example.
function debounce<T extends (...args: Parameters<T>) => ReturnType<T>>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// Usage: debounce a search input handler to fire only after the user stops typing for 300ms
const handleSearch = debounce((query: string, page: number) => {
console.log(`Searching for "${query}" on page ${page}`);
}, 300);
handleSearch("typescript", 1);
handleSearch("typescript generics", 1); // previous call is cancelled
The generic constraint T extends (...args: Parameters<T>) => ReturnType<T> preserves the original function's parameter and return types, so callers get full type checking on arguments. The returned wrapper always returns void because the delayed call's result isn't accessible to the caller.