Debounce delays execution until a pause in events, while throttle limits execution to a fixed interval — both are essential for controlling high-frequency event handlers.
Delays execution until N ms after the last event — resets the timer on each new call. Executes once when events stop. Used for search, auto-save, resize.
Executes at most once per N ms interval during continuous events. Provides consistent, evenly-spaced calls. Used for scroll, drag, rate-limiting.
Leading executes on first call immediately; trailing executes after the delay. Libraries like Lodash support both options.
Both use closures to store timer state and setTimeout for delay — debounce clears the previous timer, throttle blocks calls until the interval passes.
For visual updates, rAF is better than setTimeout-based throttle — it syncs with the browser's repaint cycle for smooth 60fps animations.
High-frequency events like scroll, resize, input, and mousemove can fire hundreds of times per second. Without rate-limiting, expensive operations (DOM updates, API calls, calculations) attached to these events cause performance problems. Debounce and throttle are the two standard patterns for controlling execution frequency.
Debounce delays execution until a specified period of inactivity. Every time the event fires, the timer resets. The function only executes once the events stop coming for the specified duration. Think of it as: "Wait until they stop, then execute."
Implementation: The debounce wrapper uses a closure to hold a timeoutId. On each call, it clears the previous timeout with clearTimeout and sets a new one. Only when the delay elapses without another call does the function actually execute.
Use cases: search input autocomplete (wait for the user to stop typing before querying the API), window resize handlers (recalculate layout once resizing is done), form auto-save (save after the user stops editing), and input validation (validate after typing pauses).
Throttle ensures a function executes at most once per specified time interval, regardless of how many times the event fires. It provides consistent, evenly-spaced executions. Think of it as: "Execute at regular intervals, no matter what."
Implementation: The throttle wrapper uses a closure to track whether the function is "cooling down." On the first call, it executes immediately and sets a flag. Subsequent calls during the cooldown period are ignored. After the interval passes, the next call is allowed through.
Use cases: scroll event handlers (update position indicator at regular intervals), drag-and-drop tracking (report position consistently), rate-limiting API calls (send analytics at most once per second), and game loop inputs (consistent frame-rate updates).
Both patterns support leading and trailing edge variants:
Libraries like Lodash provide leading and trailing options for both _.debounce and _.throttle.
For visual updates (animations, scroll effects, layout recalculations), requestAnimationFrame is often better than setTimeout-based throttle. It synchronizes with the browser's repaint cycle (~60fps), ensuring smooth animations. Pattern: store a flag, call requestAnimationFrame, execute the work inside the callback, then reset the flag.
Debounce collapses many events into one execution at the end. Throttle guarantees consistent execution at fixed intervals. Both use closures and setTimeout internally. The choice depends on whether you care about the final result (debounce) or need ongoing feedback (throttle). Being able to implement both from scratch is a common interview coding challenge.
Fun Fact
The terms debounce and throttle come from electronics and mechanical engineering respectively — debounce prevents a physical switch from registering multiple contacts when pressed once (contact bounce), and throttle valves control fluid flow rate in engines.