JavaScript Delay, Debounce, Throttle, and Async Execution
Master JavaScript’s timing mechanisms like delay
, debounce
, throttle
, setTimeout
, and setInterval
.
These tools help control execution flow, improve performance, and manage async behavior — most common question on interviews.
Delay Decorator
This example defines a delay
decorator that wraps a function and delays its execution by a specified number of milliseconds.
It preserves the correct this
context using Function.prototype.apply
, allowing methods from different
objects to be delayed without losing their binding. Useful for debouncing, throttling, or deferring side effects.
const obj1 = {
info: "obj1 info",
showInfo(...args) {
console.log(`${args}: ${this.info}`);
},
};
const obj2 = {
info: "obj2 info",
};
function delay(f, ms) {
return function (...args) {
setTimeout(() => {
// Uses apply to handle `this` dynamically
f.apply(this, args);
}, ms);
};
}
obj1.delayedShowInfo = delay(obj1.showInfo, 1000);
obj2.delayedShowInfo = delay(obj1.showInfo, 1000);
obj1.delayedShowInfo("Info", "one"); // Info,one: obj1 info
obj2.delayedShowInfo("Info"); // Info: obj2 info
Delaying function execution with and without context (this
)
This example shows how to create a delay
utility that defers a function’s execution by a
specified time using setTimeout
. It works great for standalone functions but does not preserve this
context when used with object methods.
For object methods, Function.prototype.bind
or apply
should be used to retain proper context.
function delay(f, ms) {
return function (...args) {
setTimeout(() => {
f(...args);
}, ms);
};
}
function showDetails(name, age) {
console.log(`Name: ${name}, Age: ${age}`);
}
const delayedShowDetails = delay(showDetails, 1000);
delayedShowDetails("Alice", 25);
// Name: Alice, Age: 25
const obj1 = {
info: "obj1 info",
showInfo(prefix) {
console.log(`${prefix}: ${this?.info}`);
},
};
obj1.delayedShowInfo = delay(obj1.showInfo, 1000);
obj1.delayedShowInfo("Info", "one"); // Info: undefined
Output Every Second
This example demonstrates two ways to print numbers at 1-second intervals using setInterval
and recursive setTimeout
.
Both approaches achieve the same result, but setTimeout
gives finer control over the delay between executions — especially useful for dynamic scheduling or error handling.
setInterval
function printNumbers(from, to) {
let current = from;
let timerId;
function go() {
console.log(current);
if (current === to) {
clearInterval(timerId);
}
current++;
}
go();
timerId = setInterval(go, 1000);
}
printNumbers(5, 10);
// 5 immediately, and 6 to 10 with 1s between
setTimeout
function printNumbers(from, to) {
let current = from;
function go() {
console.log(current);
if (current < to) {
setTimeout(go, 1000);
}
current++;
}
go();
}
printNumbers(5, 10);
// 5 immediately, and 6 to 10 with 1s between
throttling and debounce decorator
Compared to the debounce decorator, throttling is completely different:
debounce
runs the function once after the “cooldown” period.throttle
runs it not more often than givenms
time
debounce
function debounce(func, ms) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), ms);
};
}
const timeLoggedConsoleLog = (...args) => {
console.log(`Logged after ${Date.now() - startTime} ms:`, ...args);
};
const startTime = Date.now();
const f = debounce(timeLoggedConsoleLog, 500);
f("a");
setTimeout(() => f("b"), 200);
setTimeout(() => f("c"), 600);
setTimeout(() => f("d"), 600);
setTimeout(() => f("e"), 600); // Logged after 1118 ms: e
throttling
function throttle(fn, limit) {
let inThrottle;
return function (...args) {
if (inThrottle) return;
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
};
}
const timeLoggedConsoleLog = (...args) => {
console.log(`Logged after ${Date.now() - startTime} ms:`, ...args);
};
const startTime = Date.now();
const f = throttle(timeLoggedConsoleLog, 500);
f("a"); // Logged after 0 ms: a
setTimeout(() => f("b"), 200);
setTimeout(() => f("c"), 600); // Logged after 613 ms: c
setTimeout(() => f("d"), 600);
setTimeout(() => f("e"), 600);