debounce
Wait for a quiet interval before acting on repeated input.
Use this when A search field sends a request on every keystroke.
without the term
“The search box fires an API request on every single keystroke, so typing 'hello' sends five requests. Can you make it wait until I stop typing?”
with the term · How do you ask a model for debounce?
Debounce this search input. Replace pending work on each edit and verify an older response cannot overwrite the latest results.
What is debounce?
Debouncing postpones work until input has stopped for a chosen interval. It differs from throttling, which limits execution frequency.
Why does debounce matter when vibe coding?
Delaying every event independently still runs every request.
How do you do debounce?
Replace the pending action on new input. Handle cancellation and obsolete responses separately from the delay.
What goes wrong with debounce?
Debouncing input is treated as protection against out-of-order responses.
Try it here
Fewer requests. Still the wrong result?
First compare when requests start. Then see why an old response needs a separate guard. This simulation runs locally; it sends no search requests and does not record what you type.
1. When should a request start?
Each nonempty edit submits immediately on the left. On the right, a new edit replaces the pending submission; it waits for 300 ms of quiet. Counts are illustrative, not a benchmark.
Immediate
0 submitted
Nothing submitted yet.
Debounced · 300 ms
0 submitted
Nothing submitted yet.
Ready. Type or play the example.
2. Which response may update the screen?
Both requests below have already started, after separate pauses. We send “ca”, then “cat”. Debounce has finished its job. Choose the completion order and watch the final result.
Debounce alone
No response yet
Debounce + current-request guard
No response yet
- Ready. Run the response example.
Only the current request should own the current results.
Debounce controls starts. A current-request guard controls publication. Cancellation can also stop supported work. These are separate responsibilities.
Before and after
A search field receives 'c', 'ca', and 'cat' at 100 ms intervals. We want to submit only the latest value after 300 ms without another edit. The interval is a choice for this illustration, not a recommended setting for every product.
Before
Starting an independent timeout on every edit delays each call but retains all of them. The three inputs still cause three submissions.
function before(submit, wait) {
return (value) => {
setTimeout(() => submit(value), wait);
};
}After
Keep one pending timeout and clear it on each edit. The latest value replaces the earlier pending values, so this burst submits only 'cat'. Provide a way to cancel pending work when the field is removed.
function after(submit, wait) {
let timer;
const input = (value) => {
clearTimeout(timer);
timer = setTimeout(() => submit(value), wait);
};
input.cancel = () => clearTimeout(timer);
return input;
}Check the change
The timer-controlled example records three submissions before and one after, with no submission before the final 300 ms quiet interval. It also checks cancellation of pending work. These counts describe this input sequence only; they are not a production performance benchmark.
Save the runnable comparison, then run node --test debounce.mjs with Node.js 22.12 or later.
Download the complete teaching example: the bug, prompt, runnable code, expected result, limits, and sources, linked back to this page.
Show the assertions
test('a delay still submits every value', (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const calls = [];
const input = before((value) => calls.push(value), 300);
input('c');
t.mock.timers.tick(100);
input('ca');
t.mock.timers.tick(100);
input('cat');
t.mock.timers.tick(300);
assert.deepEqual(calls, ['c', 'ca', 'cat']);
});
test('debounce submits only after the last quiet interval', (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] });
const calls = [];
const input = after((value) => calls.push(value), 300);
input('c');
t.mock.timers.tick(100);
input('ca');
t.mock.timers.tick(100);
input('cat');
t.mock.timers.tick(299);
assert.deepEqual(calls, []);
t.mock.timers.tick(1);
assert.deepEqual(calls, ['cat']);
input('cats');
input.cancel();
t.mock.timers.tick(300);
assert.deepEqual(calls, ['cat']);
});When this is not enough
Debounce controls when work starts. It cannot stop a request that has already started or stop an older response from arriving last. Use cancellation-aware requests and guard which response may update the UI. Continuous input can postpone trailing-edge work indefinitely. If intermediate updates are necessary, consider a throttle or an explicit maximum wait instead. Clear pending timers on teardown.
The next decision
- cooperative cancellation: Handle work already in flight and prevent an obsolete completion from updating current results.
- request deduplication: Share equivalent concurrent requests when several consumers ask for the same data.
Sources and verification
- MDN: Debounce
Quiet-interval behavior and the distinction from throttling.
- MDN: AbortController
Aborting supported web requests is a separate mechanism from a debounce timer.
- Node.js test runner: MockTimers
Advancing time deterministically in the downloadable test.
Sources checked . These are illustrative examples with automated checks. How we check content.
Did this help with your task?
After trying the prompt or check, tell us what happened. This optional response records only your choice and this term.