Yevhen Klymentiev
dark
light
console
darkness
y.klymentiev@gmail.com
Reusable Snippets|Practical utility code for everyday use — custom-built and ready to share

debounce

Creates a debounced version of a function with support for immediate execution and manual cancellation.

TypeScript
Copied!
1/**
2 * Creates a debounced version of a function with support for immediate execution
3 * and manual cancellation.
4 *
5 * @param fn - The function to debounce.
6 * @param delay - Delay in milliseconds.
7 * @param immediate - If true, triggers the function on the leading edge.
8 * @returns A debounced function with a cancel method.
9 */
10export function debounce<T extends (...args: any[]) => void>(
11  fn: T,
12  delay: number,
13  immediate = false
14): ((...args: Parameters<T>) => void) & { cancel: () => void } {
15  let timeoutId: ReturnType<typeof setTimeout> | undefined;
16  let called = false;
17
18  const debounced = (...args: Parameters<T>) => {
19    const callNow = immediate && !called;
20
21    if (timeoutId) clearTimeout(timeoutId);
22
23    timeoutId = setTimeout(() => {
24      timeoutId = undefined;
25      if (!immediate) fn(...args);
26      called = false;
27    }, delay);
28
29    if (callNow) {
30      fn(...args);
31      called = true;
32    }
33  };
34
35  debounced.cancel = () => {
36    if (timeoutId) {
37      clearTimeout(timeoutId);
38      timeoutId = undefined;
39    }
40    called = false;
41  };
42
43  return debounced;
44}
  • Delay-Based Throttling

    Limits how frequently a function can execute by grouping rapid calls into a single invocation, reducing unnecessary processing.

  • Supports Leading Edge Execution

    The immediate flag allows execution on the first call, which is useful for responsive UIs and instant feedback.

  • Manual Cancellation

    Provides a .cancel() method to programmatically clear the pending execution, adding flexibility in teardown or reset scenarios.

  • Type-Safe and Flexible

    Uses TypeScript generics to preserve argument and return types for any input function, improving developer experience and safety.

  • Non-Intrusive and Pure

    Wraps any function without modifying its behavior outside of timing, making it a non-invasive utility.

Tests | Examples

TypeScript
Copied!
1jest.useFakeTimers();
2
3test('debounce - should delay execution', () => {
4  const fn = jest.fn();
5  const debounced = debounce(fn, 1000);
6
7  debounced();
8  debounced();
9  debounced();
10
11  expect(fn).not.toBeCalled();
12
13  jest.advanceTimersByTime(1000);
14
15  expect(fn).toBeCalledTimes(1);
16});
17
18test('debounce - should call immediately when immediate=true', () => {
19  const fn = jest.fn();
20  const debounced = debounce(fn, 1000, true);
21
22  debounced('first');
23  debounced('second');
24  debounced('third');
25
26  expect(fn).toHaveBeenCalledTimes(1);
27  expect(fn).toHaveBeenCalledWith('first');
28
29  jest.advanceTimersByTime(1000);
30
31  debounced('afterWait');
32  expect(fn).toHaveBeenCalledTimes(2);
33  expect(fn).toHaveBeenLastCalledWith('afterWait');
34});
35
36test('debounce - should cancel pending call', () => {
37  const fn = jest.fn();
38  const debounced = debounce(fn, 1000);
39
40  debounced();
41  debounced.cancel();
42
43  jest.advanceTimersByTime(1000);
44  expect(fn).not.toBeCalled();
45});

Common Use Cases

  • Input Event Optimization

    Prevent excessive API calls or UI updates when users type into a search box or input field.

  • Window Resize or Scroll Handlers

    Reduce frequency of layout recalculations or heavy computations tied to rapid DOM events.

  • Auto-Save or Auto-Sync Features

    Debounce form or editor changes before triggering save actions to minimize network load.

  • Analytics and Tracking

    Limit frequency of logging or tracking events tied to user interaction or navigation.

  • Component Cleanup in React/Vue

    Use .cancel() during unmounting to avoid triggering state updates on unmounted components.

Codebase: Utilities -> Functions -> debounce | Yevhen Klymentiev