JavaScript Timers
Schedule code execution using setTimeout and setInterval, and rate-limit handlers using debounce and throttle patterns.
TL;DR
- 01Schedule delayed or repeating code using
setTimeoutandsetInterval. - 02Cancel pending asynchronous timer callbacks using clear methods.
- 03Rate-limit frequent event triggers with debounce and throttle functions.
Tips
- 01Always store the timer ID returned by
setTimeoutorsetIntervalto cancel execution when conditions change. - 02Prefer
requestAnimationFrameoversetIntervalwhen creating web animations to match the display refresh rate.
Warnings
- 01Remember that timer delays are minimums rather than exact guarantees due to event loop call stack blocking.
- 02Forgetting to clear an active
setIntervalloop creates memory leaks that persist throughout application life.
setTimeout and clearTimeout
setTimeoutSchedules a single callback execution after a specified millisecond delay.
setTimeout(() => {
console.log("Runs after 1 second");
}, 1000);Timer cancellationCancels a scheduled setTimeout callback before execution using its timer ID.
const id = setTimeout(() => console.log("done"), 5000);
clearTimeout(id);Forwarding argumentsPasses extra arguments directly into the timer callback function handler.
setTimeout(u => console.log(u), 1000, "Alice");Zero delayQueues a callback at the end of the current execution stack immediately.
console.log("first");
setTimeout(() => console.log("third"), 0);
console.log("second");setInterval and clearInterval
setIntervalSchedules repeated callback execution on a fixed time interval loop.
const id = setInterval(() => {
console.log("tick");
}, 1000);Self-clearing intervalStops a repeating interval internally once a counter threshold is met.
let count = 0;
const id = setInterval(() => {
count++;
if (count >= 5) clearInterval(id);
}, 1000);Poller cleanupsClears active poll intervals during cleanups to prevent resource leaks.
function poll() {
const id = setInterval(fetchData, 5000);
return () => clearInterval(id); // clean
}Recursive timeoutChains setTimeouts recursively to ensure steady spacing between variable runs.
function poll() {
doWork();
setTimeout(poll, 1000);
}
poll();Timer Precision and Event Loop
Main thread blocksTimers await thread clearance, causing late execution if synchronous blocks run.
setTimeout(() => console.log("late"), 0);
while (Date.now() < start + 200) {} // blockSequential queueingLong synchronous execution blocks delay all queued callbacks concurrently.
setTimeout(() => console.log("A"), 10);
setTimeout(() => console.log("B"), 20);
// delayed together if thread is busyMacrotask queueingTimers run as macrotasks, resolving after synchronous code and microtasks.
console.log("1");
setTimeout(() => console.log("3"), 0);
Promise.resolve().then(() => console.log("2"));Debounce and Throttle
Debounce helperDelays function execution, resetting the timer on each new invocation.
function debounce(fn, delay) {
let id;
return (...args) => {
clearTimeout(id);
id = setTimeout(() => fn(...args), delay);
};
}Input searchLimits API fetch queries by triggering only after typing pauses.
const search = debounce(query => fetch(query), 300);
input.addEventListener("input", e => {
search(e.target.value);
});Throttle helperExecutes a function at most once per fixed time interval window.
function throttle(fn, limit) {
let wait = false;
return (...args) => {
if (wait) return;
fn(...args);
wait = true;
setTimeout(() => { wait = false; }, limit);
};
}Scroll trackingProtects browser scroll listeners from triggering expensive paint redraws.
const logScroll = throttle(() => updateUI(), 200);
window.addEventListener("scroll", logScroll);requestAnimationFrame
rAF animation loopSchedules callbacks right before the browser repaints active screen elements.
function animate() {
moveElement();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);Animation stopTerminates a requestAnimationFrame animation loop using the returned ID.
const id = requestAnimationFrame(animate);
cancelAnimationFrame(id);Scroll syncSynchronizes visual adjustments directly with browser repaint refresh frames.
let ticking = false;
window.addEventListener("scroll", () => {
if (!ticking) {
requestAnimationFrame(() => {
updateScroll();
ticking = false;
});
ticking = true;
}
});In Practice
Creates a search debouncer that delays query submissions and offers a cleanup method to prevent memory leaks.
- 01Establish a local variable to store the active timer identifier.
- 02Clear any pending search timeouts upon receiving new user key inputs.
- 03Schedule a new timeout handler to submit queries after three hundred milliseconds.
- 04Define a destroy function to cancel any outstanding timers during unmounting.
- 05Return the event handler and cleanup callbacks to the caller.
function createSearchInput(onSearch) {
let timerId;
function handleInput(event) {
clearTimeout(timerId);
const query = event.target.value;
timerId = setTimeout(() => {
onSearch(query);
}, 300);
}
function destroy() {
clearTimeout(timerId);
}
return { handleInput, destroy };
}FAQ
JavaScript runs on a single thread. The event loop cannot run timer callbacks until the call stack is empty. Long synchronous tasks block the queue and delay timer execution.
Debouncing waits for a pause in events before running a function once. Throttling limits execution to at most once per fixed time interval. Use debouncing for search input and throttling for scrolling.
No, setTimeout callbacks remove themselves from the event queue automatically after running. You only need to call clearTimeout to cancel a pending timer before it executes.
The requestAnimationFrame method coordinates callbacks with display refresh rates, creating smoother motion. It also pauses automatically when browser tabs become inactive. This saves battery and processor power.
No, the browser prevents concurrent execution by waiting for the thread to clear. However, callbacks can queue up and fire rapidly in succession. Use recursive setTimeout for regular spacing instead.