Debounce vs. throttle

by Mike Guoynes

Debounce vs Throttle visualization graphic

Intro

User actions such as typing can trigger many events in a short amount of time.

Handling every single event leads to sluggish interfaces and wasted extraneous api requests.

Debounce and throttle are key techniques to optimize performance by controlling how frequently event handlers run.

Debounce: the food ordering analogy

πŸ‘¨β€πŸ³Waiter

Order Pad:

empty

Click buttons to add items

Idle

🍽️Kitchen

(Idle)

Imagine a waiter taking a large group's food order. People keep adding items.

The waiter doesn't run to the kitchen after every single item is mentioned. That would be inefficient!

Instead, the waiter waits for a pause in the ordering.

  • Customer adds an item ("Burger!"). Timer resets.
  • Customer adds another ("Fries!"). Timer resets again.
  • Customer pauses...
  • After the pause (debounce timeout), the waiter submits the complete order to the kitchen.

Debouncing is like this waiter: it only performs the action (submitting the order / calling the function) after the events (adding items / user input) have stopped for a specific duration.

Debounce use case: autocomplete search

API

Debounce prevents sending a search request for every keystroke, waiting until the user pauses typing.

Debounce code

Try it out! Type "san francisco" slowly into the search box below and watch how the api request is debounced until you pause.

Add debounce to your toolchest. Use it for search boxes, autosave, or choose your own adventure. Leverage it, own it. It's yours now.


function debounce(fn, gapTime) {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn(...args);
    }, gapTime);
  };
}
                            

Alright, let's break down this debounce function! It's a higher-order function, meaning it takes a function as an argument and returns a *new* function.

Here are the key ingredients:

1. Fn (your function): This is the actual workhorse function you want to delay, like making an API call or updating the UI. You pass this into debounce.

2. Timer (the magic variable): Inside debounce, we declare a timer variable. Thanks to the power of closures ✨, this variable sticks around between calls to the *returned* function.

When the returned function gets called (e.g., on every keystroke), it first nukes any existing timer using clearTimeout(timer). Then, it sets a *brand new* setTimeout. Think of it as constantly resetting a countdown timer.

Your original function fn only gets a chance to run if that setTimeout actually finishes its countdown (gapTime milliseconds) without being cleared by another rapid call. Cool, right?

Throttle

Events are processed at fixed intervals.

Throttle visualization showing function calls at fixed intervals

Unlike debounce which waits for a pause, throttle executes your function at most once every x milliseconds, regardless of how many events fire during that interval.

Think of it as a rate limiter: it guarantees a regular rhythm of execution while events are happening.

Throttle: the rollercoaster line analogy

πŸšΆβ€β™‚οΈπŸšΆβ€β™€οΈWaiting Line
0

People Waiting

🎒

Click button to join line

Station Idle

πŸŽ‰# Riders Served!
0

Happy Riders!

Imagine a rollercoaster that departs every 3 seconds (our throttle interval).

People (events) can join the line anytime. However, the rollercoaster doesn't wait for the line to stop growing.

It departs strictly on its schedule, taking whoever is at the front of the line.

  • Someone joins the line. The 3s departure timer starts.
  • More people join. The timer continues unaffected.
  • After 3 seconds, the ride departs with the people at the front.
  • If people are still in line, the 3s timer immediately starts again for the *next* departure.

Throttling is like this rollercoaster: it performs the action (dispatching the ride / calling the function) at a fixed maximum rate, regardless of how many events (people joining) occurred during the interval.

Throttle code

Try it out! Scroll quickly within the box below and observe how the position update is throttled.

Add throttle to your toolkit. Use it for scroll handlers, resizing, or any event that fires rapidly. Control the flow.

const throttle = (fn, delay) => {
  let isPending = false;
  let pendingArguments;

  return function throttleWrapper(...args) {
    const isReady = !isPending;

    if (isReady) {
      fn(...args); // Call when ready
      pendingArguments = undefined;
      isPending = true;

      // Create a new timer
      setTimeout(() => {
        isPending = false; // After timeout -> ready.
        if (pendingArguments) {
           throttleWrapper(...pendingArguments);
        }
      }, delay);
    } else {
      pendingArguments = args; // Store the latest arguments.
    }
  };
};

Think of throttle like a turnstile at a subway station. It only lets one person through every few seconds, no matter how many people are lined up.

Key Difference from Debounce:

Unlike debounce (which resets its timer with every new event), throttle guarantees execution at regular intervals. It doesn't care if events keep happening; it just lets one "through the gate" every delay milliseconds.

How it Works - The Essentials:

  • The Function (fn): The task you want to perform (e.g., update scroll position).
  • The Interval (delay): The minimum time that must pass between executions. Like the turnstile's fixed rotation time.
  • The Gatekeeper (throttleWrapper):
    • When called, it checks if the "gate" is open (enough time has passed).
    • If open: Runs your fn immediately & closes the gate for the delay.
    • If closed: It might remember the *latest* call attempt. When the gate reopens, it could execute using those remembered arguments.

Use Cases:

Perfect for rate-limiting events that fire continuously, like scroll handlers, window resize events, or dragging operations.

Try it, write this code on paper. Then code it. Try to break it. Reverse engineer it.

Add throttle to your toolkit. Use it for scroll handlers, resizing, or any event that fires rapidly. Control the flow. Leverage it. Own it. It's yours now.

Try it out! Move your mouse or finger across the interaction pad below to see debounce and throttle in action.

Events
Debounce (500ms)
Throttle (300ms)
πŸ‘†Move Mouse / Touch Here

How They Work

Debounce

Waits for a pause (500ms) in events. Triggers only once after the last event in a rapid series stops for the specified duration. Good for actions like auto-saving or search suggestions after typing stops.

Throttle

Limits execution to once every interval (300ms), regardless of how many events fire. Ensures a function runs regularly but not excessively. Useful for rate-limiting scroll or resize handlers.