You've built a basic debounce that defers a call until the user stops triggering events for wait milliseconds. In real applications you almost always need two more controls on top of that: a way to throw the pending call away, and a way to run it right now instead of waiting. Your job is to extend debounce so the returned function carries cancel() and flush() methods that do exactly that.
function debounceIi(
fn: (...args: any[]) => any,
wait: number
): {
(...args: any[]): void;
cancel(): void; // abort any pending invocation; fn is NOT called
flush(): void; // invoke any pending invocation immediately; no-op if nothing is pending
};
The returned value is a function. cancel and flush are properties attached to that function.
// 1) cancel() throws away the pending call
const d = debounceIi(console.log, 100);
d('hello');
d.cancel();
// 100ms later: nothing prints. The pending call was discarded.
// 2) flush() runs the pending call right now with the latest args
const d = debounceIi(console.log, 1000);
d('a');
d('b');
d.flush(); // prints 'b' immediately — does NOT wait the remaining ~1000ms
d.flush(); // no-op: nothing is pending anymore
d(...) until the timer fires (or cancel/flush resolves it).fn must NOT be invoked. Same after a prior flush or cancel.this — if the user attaches the debounced function as an object method (obj.save = debouncedIi(...)) and calls obj.save(), the eventual fn invocation should see obj as its this.Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.