Readme of @shortkit/debounce-throttle

18/01/2026 • 4 min read

A tiny, executor-style debounce and throttle utility. Unlike wrapper-based APIs, you call debounce(fn, wait, ...args) directly each time. The state is tracked internally via a WeakMap keyed by the function reference.

Installation

npm i @shortkit/debounce-throttle

Usage

debounce(fn, wait, ...args)

Delays function execution until after a specified wait period of inactivity. Every call resets the timer.

Signature:

declare function debounce<F extends (...args: any[]) => any>(
  fn: F,
  wait: number,
  ...args: Parameters<F>
): void

Example:

import { debounce } from "@shortkit/debounce-throttle"

const saveToServer = (data: string) => console.log("Saving:", data)

// Each call resets the 300ms timer
input.addEventListener("input", (e) => {
  debounce(saveToServer, 300, e.target.value)
})

throttle(fn, wait, ...args)

Executes the function immediately on first call, then blocks subsequent calls until the wait period expires. Fires a trailing call with the last arguments.

Signature:

declare function throttle<F extends (...args: any[]) => any>(
  fn: F,
  wait: number,
  ...args: Parameters<F>
): void

Example:

import { throttle } from "@shortkit/debounce-throttle"

const updatePosition = (x: number, y: number) => console.log("Position:", x, y)

// Executes at most once per 100ms
document.addEventListener("mousemove", (e) => {
  throttle(updatePosition, 100, e.clientX, e.clientY)
})

debounce(id, fn, wait, ...args) / throttle(id, fn, wait, ...args)

Use a symbol as the key instead of the function reference. This is useful when you need to debounce/throttle anonymous functions or arrow functions that are recreated on each render.

Signature:

declare function debounce<F extends (...args: any[]) => any>(
  id: symbol,
  fn: F,
  wait: number,
  ...args: Parameters<F>
): void

declare function throttle<F extends (...args: any[]) => any>(
  id: symbol,
  fn: F,
  wait: number,
  ...args: Parameters<F>
): void

Example:

import { debounce } from "@shortkit/debounce-throttle"

// Define symbol outside component (global scope)
const SEARCH_ID = Symbol("search")

export function SearchInput() {
  const [query, setQuery] = useState("")

  // Arrow function is recreated each render, but symbol keeps state consistent
  return (
    <input
      value={query}
      onChange={(e) => {
        setQuery(e.target.value)
        debounce(SEARCH_ID, () => fetchResults(e.target.value), 300)
      }}
    />
  )
}

State for symbol-keyed calls is automatically cleaned up after execution completes.


debounce.withOptions(opts) / throttle.withOptions(opts)

Create a customized executor with different options. Works with both function and symbol keys.

Options:

type Options = {
  leading?: boolean   // Execute on leading edge (default: false for debounce, true for throttle)
  trailing?: boolean  // Execute on trailing edge (default: true)
  maxWait?: number    // Maximum time to wait before forced execution
}

Examples:

import { debounce, throttle } from "@shortkit/debounce-throttle"

// Debounce with leading call
const debounceLeading = debounce.withOptions({ leading: true })
debounceLeading(fn, 100, arg1, arg2)

// Debounce with maxWait (guaranteed execution every 500ms)
const debounceMax = debounce.withOptions({ maxWait: 500 })
debounceMax(fn, 100, arg1)

// Throttle without trailing call
const throttleNoTrail = throttle.withOptions({ trailing: false })
throttleNoTrail(fn, 100, arg1)

// Works with symbol keys too
const id = Symbol("custom")
debounceMax(id, fn, 100, arg1)

cancel(key)

Cancels any pending execution for the given function or symbol.

import { debounce, cancel } from "@shortkit/debounce-throttle"

debounce(saveData, 1000, data)
cancel(saveData) // returns true if there was something to cancel

// With symbol
const id = Symbol("save")
debounce(id, saveData, 1000, data)
cancel(id) // returns true if there was something to cancel

flush(key)

Immediately executes any pending call for the given function or symbol.

import { debounce, flush } from "@shortkit/debounce-throttle"

debounce(saveData, 1000, data)
flush(saveData) // returns true if there was a pending call

// With symbol
const id = Symbol("save")
debounce(id, saveData, 1000, data)
flush(id) // returns true if there was a pending call

Debounce vs Throttle

AspectDebounceThrottle
First callWaits (unless leading: true)Executes immediately
During activityKeeps resetting timerBlocks until interval expires
Use caseFinal state after events stopConsistent updates during activity

Debounce: Groups rapid calls into a single execution after activity stops.

Throttle: Spreads calls across fixed intervals for periodic execution.

Strategies for rate-limiting - x.com@itsalexzajac 7:30 AM · Dec 17, 2024

Notes

  • Function keys: State is tracked per function reference using a WeakMap and automatically garbage collected when no longer referenced.
  • Symbol keys: State is stored in a Map and cleaned up after execution completes (timer expires or flush is called).
  • Both cancel and flush return a boolean indicating whether there was a pending call.

"Readme of @shortkit/debounce-throttle", 18/01/2026, 22:21:00

#javascript, #npm, #readme, #web-development

Loading comments...