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.
Order Pad:
empty
Click buttons to add items
Idle
(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.
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 prevents sending a search request for every keystroke, waiting until the user pauses typing.
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?
Events are processed 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.
People Waiting
Click button to join line
Station Idle
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.
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.
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:
fn
): The task you want to perform (e.g., update scroll position).delay
): The minimum time that must pass between executions. Like the turnstile's fixed rotation time.throttleWrapper
):fn
immediately & closes the gate for the delay
.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.
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.
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.