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

throttle

Creates a throttled version of a function.

TypeScript
Copied!
1/**
2 * Creates a throttled version of a function.
3 *
4 * @param fn - Function to throttle.
5 * @param interval - Minimum time (ms) between calls.
6 * @param options - Optional config:
7 *   - leading: Call on the leading edge (default: true)
8 *   - trailing: Call on the trailing edge (default: true)
9 * @returns A throttled function with a cancel method.
10 */
11export function throttle<T extends (...args: any[]) => void>(
12  fn: T,
13  interval: number,
14  options: { leading?: boolean; trailing?: boolean } = {}
15): ((...args: Parameters<T>) => void) & { cancel: () => void } {
16  const { leading = true, trailing = true } = options;
17  let timeoutId: ReturnType<typeof setTimeout> | null = null;
18  let lastCallTime: number | null = null;
19  let savedArgs: Parameters<T> | null = null;
20
21  const throttled = (...args: Parameters<T>) => {
22    const now = Date.now();
23
24    if (lastCallTime === null && !leading) {
25      lastCallTime = now;
26    }
27
28    const remaining = interval - (now - (lastCallTime ?? 0));
29
30    savedArgs = args;
31
32    if (remaining <= 0 || lastCallTime === null) {
33      if (timeoutId) {
34        clearTimeout(timeoutId);
35        timeoutId = null;
36      }
37      fn(...args);
38      lastCallTime = now;
39    } else if (trailing && !timeoutId) {
40      timeoutId = setTimeout(() => {
41        timeoutId = null;
42        if (savedArgs) {
43          fn(...savedArgs);
44          lastCallTime = Date.now();
45          savedArgs = null;
46        }
47      }, remaining);
48    }
49  };
50
51  throttled.cancel = () => {
52    if (timeoutId) clearTimeout(timeoutId);
53    timeoutId = null;
54    lastCallTime = null;
55    savedArgs = null;
56  };
57
58  return throttled;
59}
  • Dual Edge Support (Leading & Trailing)

    Offers fine-grained control over whether the function is called at the start, end, or both ends of a throttle interval.

  • Stateful Delay Management

    Tracks call timing and arguments internally to handle pending trailing executions reliably.

  • Manual Cancellation

    Exposes a .cancel() method for explicit control over cleanup, ideal for component unmounting or lifecycle hooks.

  • Type-Safe and Flexible

    Fully generic in TypeScript, preserving function argument and return types for improved type safety and autocomplete.

  • Performance Optimization

    Prevents excessive invocations of high-frequency functions (like event handlers), improving UI responsiveness and CPU efficiency.

Tests | Examples

TypeScript
Copied!
1jest.useFakeTimers();
2
3test('throttle - basic throttling', () => {
4  const fn = jest.fn();
5  const throttled = throttle(fn, 1000);
6
7  throttled();
8  throttled();
9  throttled();
10
11  expect(fn).toHaveBeenCalledTimes(1);
12
13  jest.advanceTimersByTime(1000);
14  throttled();
15
16  expect(fn).toHaveBeenCalledTimes(2);
17});
18
19test('throttle - leading: false, trailing: true', () => {
20  const fn = jest.fn();
21  const throttled = throttle(fn, 1000, { leading: false, trailing: true });
22
23  throttled();
24  throttled();
25  throttled();
26
27  expect(fn).toHaveBeenCalledTimes(0);
28
29  jest.advanceTimersByTime(1000);
30  expect(fn).toHaveBeenCalledTimes(1);
31});
32
33test('throttle - trailing: false, only leading', () => {
34  const fn = jest.fn();
35  const throttled = throttle(fn, 1000, { trailing: false });
36
37  throttled();
38  throttled();
39  throttled();
40
41  expect(fn).toHaveBeenCalledTimes(1);
42
43  jest.advanceTimersByTime(1000);
44  expect(fn).toHaveBeenCalledTimes(1);
45});
46
47test('throttle - cancel works', () => {
48  const fn = jest.fn();
49  const throttled = throttle(fn, 1000);
50
51  throttled();
52  throttled();
53
54  throttled.cancel();
55
56  jest.advanceTimersByTime(1000);
57  expect(fn).toHaveBeenCalledTimes(1); // trailing call canceled
58});

Common Use Cases

  • Scroll or Resize Event Handling

    Throttle expensive layout recalculations or UI updates during frequent DOM events.

  • Mouse or Touch Movement Tracking

    Limit position-based updates (e.g., drag-and-drop, drawing) to a reasonable rate.

  • Button or Action Debouncing

    Prevent multiple rapid clicks or taps from triggering duplicate requests or transitions.

  • Auto-Save or Auto-Sync

    Periodically save input changes without overwhelming the backend or local storage.

  • Analytics & Metrics Throttling

    Reduce the frequency of metric collection or log submissions during high-activity sessions.

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