README
2 min read18 headings
9.5 Timers and Intervals
Overview
JavaScript provides built-in timing functions that allow you to schedule code execution in the future. These functions are essential for creating delays, animations, polling, debouncing, throttling, and other time-based behaviors.
Core Timer Functions
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TIMER FUNCTIONS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β setTimeout(callback, delay) β
β βββ Executes callback ONCE after delay (ms) β
β βββ Returns timerId for cancellation β
β β
β setInterval(callback, interval) β
β βββ Executes callback REPEATEDLY every interval (ms) β
β βββ Returns intervalId for cancellation β
β β
β clearTimeout(timerId) β
β βββ Cancels a scheduled timeout β
β β
β clearInterval(intervalId) β
β βββ Stops an interval β
β β
β requestAnimationFrame(callback) β
β βββ Schedules callback before next repaint (~60fps) β
β βββ Optimal for visual animations β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
setTimeout
Basic Usage
// Execute once after 1 second
setTimeout(() => {
console.log('Hello after 1 second!');
}, 1000);
// With function reference
function greet(name) {
console.log(`Hello, ${name}!`);
}
setTimeout(greet, 2000, 'Alice'); // Pass arguments after delay
Canceling a Timeout
const timerId = setTimeout(() => {
console.log("This won't run");
}, 5000);
// Cancel before it executes
clearTimeout(timerId);
Zero Delay
// Zero delay doesn't mean immediate execution
// It schedules for next event loop iteration
console.log('1');
setTimeout(() => {
console.log('3'); // Runs after sync code completes
}, 0);
console.log('2');
// Output: 1, 2, 3
setInterval
Basic Usage
// Execute every 2 seconds
let count = 0;
const intervalId = setInterval(() => {
count++;
console.log(`Tick ${count}`);
// Stop after 5 ticks
if (count >= 5) {
clearInterval(intervalId);
}
}, 2000);
Interval Timing Diagram
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β setInterval TIMING β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Time: 0ms 1000ms 2000ms 3000ms 4000ms β
β β β β β β β
β Start βββ€ β β β β β
β β ββββββ€ ββββββ€ ββββββ€ ββββββ€ β
β β βCB 1β βCB 2β βCB 3β βCB 4β β
β β ββββββ ββββββ ββββββ ββββββ β
β β
β Note: Interval is measured from START of each callback β
β If callback takes longer than interval, execution β
β may overlap or queue β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Timer Accuracy
Minimum Delay
| Environment | Minimum Delay |
|---|---|
| Active tab | ~4ms (browser-enforced minimum) |
| Background tab | 1000ms+ (throttled) |
| Node.js | ~1ms |
Drift in Intervals
// Problem: Drift accumulates over time
let expected = Date.now();
let ticks = 0;
const intervalId = setInterval(() => {
const now = Date.now();
const drift = now - expected;
console.log(`Tick ${++ticks}, drift: ${drift}ms`);
expected += 1000; // Expected next tick
if (ticks >= 10) clearInterval(intervalId);
}, 1000);
Self-Correcting Timer
function accurateInterval(callback, interval) {
let expected = Date.now() + interval;
function step() {
const drift = Date.now() - expected;
callback();
expected += interval;
setTimeout(step, Math.max(0, interval - drift));
}
setTimeout(step, interval);
}
Common Patterns
Pattern 1: Delay Promise
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Usage
async function example() {
console.log('Start');
await delay(1000);
console.log('After 1 second');
}
Pattern 2: Timeout Promise
function timeout(ms, message = 'Timeout') {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error(message)), ms);
});
}
// Race against timeout
Promise.race([fetch('/api/data'), timeout(5000, 'Request timed out')]);
Pattern 3: Debounce
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEBOUNCE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Events: β β β β β β β β
β Time: βββββΌββββΌββββΌββββΌββββΌβββββββββββββββββββββΌββββΌβββββ β
β β
β Wait: ββββββ 500ms ββββββ β
β β
β Fire: β β
β β
β Description: Executes AFTER a pause in events β
β Use case: Search input, resize handler β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// Usage: Search input
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((query) => {
console.log('Searching:', query);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Pattern 4: Throttle
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β THROTTLE β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Events: β β β β β β β β β β β
β Time: βββββΌββββΌββββΌββββΌββββΌββββΌββββΌββββΌββββΌββββΌβββ β
β β
β Window: βββ 500ms βββββ 500ms βββββ 500ms βββ β
β β
β Fire: β β β β β
β β
β Description: Executes AT MOST once per time window β
β Use case: Scroll handler, button clicks β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function throttle(fn, limit) {
let inThrottle = false;
return function (...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Usage: Scroll handler
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledScroll);
Debounce vs Throttle Comparison
| Aspect | Debounce | Throttle |
|---|---|---|
| Timing | After quiet period | At regular intervals |
| First call | Delayed | Immediate |
| Use case | Search, resize | Scroll, mousemove |
| Rate | 1 call after events stop | Max N calls per second |
requestAnimationFrame
Basic Usage
function animate() {
// Update animation
element.style.left = parseFloat(element.style.left) + 1 + 'px';
// Continue animation
if (parseFloat(element.style.left) < 500) {
requestAnimationFrame(animate);
}
}
// Start animation
requestAnimationFrame(animate);
Advantages Over setInterval
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β requestAnimationFrame vs setInterval β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β setInterval(callback, 16) (~60fps) β
β βββ β Not synchronized with display refresh β
β βββ β May cause jank/tearing β
β βββ β Runs in background tabs (wastes resources) β
β βββ β May fire during browser layout/paint β
β β
β requestAnimationFrame(callback) β
β βββ β Synchronized with display refresh β
β βββ β Smooth animations β
β βββ β Pauses in background tabs β
β βββ β Optimized by browser β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Animation with Delta Time
let lastTime = 0;
function animate(currentTime) {
const deltaTime = currentTime - lastTime;
lastTime = currentTime;
// Move 100 pixels per second regardless of frame rate
const speed = 100; // pixels per second
const distance = (speed * deltaTime) / 1000;
element.style.left = parseFloat(element.style.left) + distance + 'px';
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Polling Pattern
class Poller {
constructor(asyncFn, interval) {
this.asyncFn = asyncFn;
this.interval = interval;
this.running = false;
}
start() {
this.running = true;
this.poll();
}
stop() {
this.running = false;
}
async poll() {
if (!this.running) return;
try {
const result = await this.asyncFn();
console.log('Poll result:', result);
} catch (error) {
console.error('Poll error:', error);
}
setTimeout(() => this.poll(), this.interval);
}
}
// Usage
const poller = new Poller(
() => fetch('/api/status').then((r) => r.json()),
5000
);
poller.start();
// Later: poller.stop();
Timer Queue and Event Loop
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EVENT LOOP & TIMERS β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββ β
β β Call Stack β βββ Synchronous code executes here β
β ββββββββ¬ββββββββ β
β β β
β βΌ β
β ββββββββββββββββ β
β β Microtasks β βββ Promise callbacks (.then, async/await) β
β β Queue β Processed after each sync task β
β ββββββββ¬ββββββββ β
β β β
β βΌ β
β ββββββββββββββββ β
β β Macrotasks β βββ setTimeout, setInterval callbacks β
β β Queue β Processed one at a time β
β ββββββββββββββββ β
β β
β Order: Sync β All Microtasks β One Macrotask β Repeat β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Example: Task Ordering
console.log('1. Sync');
setTimeout(() => console.log('4. Timeout'), 0);
Promise.resolve().then(() => console.log('3. Promise'));
console.log('2. Sync');
// Output:
// 1. Sync
// 2. Sync
// 3. Promise
// 4. Timeout
Best Practices
1. Always Clean Up Timers
class Component {
constructor() {
this.timers = [];
}
setTimeout(fn, delay) {
const id = setTimeout(fn, delay);
this.timers.push({ type: 'timeout', id });
return id;
}
setInterval(fn, interval) {
const id = setInterval(fn, interval);
this.timers.push({ type: 'interval', id });
return id;
}
destroy() {
this.timers.forEach((timer) => {
if (timer.type === 'timeout') {
clearTimeout(timer.id);
} else {
clearInterval(timer.id);
}
});
this.timers = [];
}
}
2. Use AbortController for Cancellation
function cancellableDelay(ms, signal) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timeoutId);
reject(new DOMException('Aborted', 'AbortError'));
});
});
}
// Usage
const controller = new AbortController();
cancellableDelay(5000, controller.signal)
.then(() => console.log('Completed'))
.catch((err) => console.log('Cancelled:', err.message));
// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);
3. Avoid Nested Timers When Possible
// β Messy nested timers
setTimeout(() => {
doStep1();
setTimeout(() => {
doStep2();
setTimeout(() => {
doStep3();
}, 1000);
}, 1000);
}, 1000);
// β
Clean with async/await
async function sequence() {
await delay(1000);
doStep1();
await delay(1000);
doStep2();
await delay(1000);
doStep3();
}
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Memory leaks | Timers keep references | Clear on cleanup |
this binding | Wrong context in callback | Use arrow functions |
| String callbacks | setTimeout("code", 100) | Always use functions |
| Background throttling | Intervals slowed | Use visibility API |
| Accumulated drift | Timing inaccuracy | Use self-correcting timers |
Key Takeaways
- setTimeout - one-time delayed execution
- setInterval - repeated execution at intervals
- Always clear timers - prevent memory leaks
- Use requestAnimationFrame - for visual animations
- Debounce - wait for pause in events
- Throttle - limit execution rate
- Timers are async - they go through the event loop
- Timing is not guaranteed - delays are minimum, not exact