READMEJavaScript

README

Module 15 Asynchronous JavaScript / .5 Timers Intervals

Concept Lesson
Advanced
4 min

Learning Objective

Understand Module 15 Asynchronous JavaScript well enough to explain it, recognize it in JavaScript, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

AsynchronousJavascriptCore Timer FunctionsSettimeoutBasic Usage
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

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

EnvironmentMinimum Delay
Active tab~4ms (browser-enforced minimum)
Background tab1000ms+ (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

AspectDebounceThrottle
TimingAfter quiet periodAt regular intervals
First callDelayedImmediate
Use caseSearch, resizeScroll, mousemove
Rate1 call after events stopMax 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

PitfallProblemSolution
Memory leaksTimers keep referencesClear on cleanup
this bindingWrong context in callbackUse arrow functions
String callbackssetTimeout("code", 100)Always use functions
Background throttlingIntervals slowedUse visibility API
Accumulated driftTiming inaccuracyUse self-correcting timers

Key Takeaways

  1. setTimeout - one-time delayed execution
  2. setInterval - repeated execution at intervals
  3. Always clear timers - prevent memory leaks
  4. Use requestAnimationFrame - for visual animations
  5. Debounce - wait for pause in events
  6. Throttle - limit execution rate
  7. Timers are async - they go through the event loop
  8. Timing is not guaranteed - delays are minimum, not exact

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue