
Implementing Debounce and Throttle in Vanilla JavaScript
Attaching heavy DOM operations or network requests directly to scroll, resize, or keyup events can fire dozens of times per second and freeze the browser's main thread. Implementing debounce and throttle in vanilla JavaScript lets you control how often a handler executes without pulling in an external utility library.
- Debounce groups a burst of rapid calls into a single execution after the user stops triggering the event for a specified delay (ideal for search-input autocomplete).
- Throttle guarantees that a function executes at most once every
limitmilliseconds during continuous firing (ideal for scroll progress or window resize handlers).
Example
User types "clean" in 300ms (5 keyup events):
- Without debounce: 5 API calls fired
- With debounce(fn, 250): 1 API call fired 250ms after typing stops
Input : Target callback function fn and delay in milliseconds wait
Output : Rate-limited wrapper function preserving this context and arguments.
Implementation
// Debounce: Waits until `wait` ms have elapsed since the last invocation
function debounce(fn, wait) {
var timeoutId = null;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
fn.apply(context, args);
}, wait);
};
}
// Throttle: Executes at most once every `limit` ms
function throttle(fn, limit) {
var inThrottle = false;
return function () {
var context = this;
var args = arguments;
if (!inThrottle) {
fn.apply(context, args);
inThrottle = true;
setTimeout(function () {
inThrottle = false;
}, limit);
}
};
}
// Usage Example on DOM Events
var searchInput = document.querySelector("#search-input");
if (searchInput) {
searchInput.addEventListener(
"input",
debounce(function (event) {
console.log("Fetching search results for:", event.target.value);
}, 300)
);
}
Main Point: Closures allow timeoutId and inThrottle state to persist privately across event invocations while keeping the global namespace clean.
Credits
Photo by Clement H