# debounce: a worked example

Canonical page: https://vibe-coding-101.brighteng.org/c/debounce/#before-and-after

## The problem

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.

## Prompt to paste

Debounce this search input. Replace pending work on each edit and verify an older response cannot overwrite the latest results.

## Before

Starting an independent timeout on every edit delays each call but retains all of them. The three inputs still cause three submissions.

## 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.

## Run the check

Save the following code as debounce.mjs and run `node --test debounce.mjs` with Node.js 22.12 or later.

```js
import test from 'node:test';
import assert from 'node:assert/strict';

function before(submit, wait) {
  return (value) => {
    setTimeout(() => submit(value), wait);
  };
}

function after(submit, wait) {
  let timer;
  const input = (value) => {
    clearTimeout(timer);
    timer = setTimeout(() => submit(value), wait);
  };
  input.cancel = () => clearTimeout(timer);
  return input;
}

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']);
});
```

## Expected result

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.

## Limits

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.

## Sources

- [MDN: Debounce](https://developer.mozilla.org/en-US/docs/Glossary/Debounce): Quiet-interval behavior and the distinction from throttling.
- [MDN: AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController): Aborting supported web requests is a separate mechanism from a debounce timer.
- [Node.js test runner: MockTimers](https://nodejs.org/api/test.html#class-mocktimers): Advancing time deterministically in the downloadable test.

Sources checked: 2026-10-11. These are original illustrative examples with automated checks. Human editorial approval is not recorded. Policy: https://vibe-coding-101.brighteng.org/editorial/
