JavaScript Timers

Schedule code execution using setTimeout and setInterval, and rate-limit handlers using debounce and throttle patterns.

TL;DR

  1. 01Schedule delayed or repeating code using setTimeout and setInterval.
  2. 02Cancel pending asynchronous timer callbacks using clear methods.
  3. 03Rate-limit frequent event triggers with debounce and throttle functions.

Tips

  1. 01Always store the timer ID returned by setTimeout or setInterval to cancel execution when conditions change.
  2. 02Prefer requestAnimationFrame over setInterval when creating web animations to match the display refresh rate.

Warnings

  1. 01Remember that timer delays are minimums rather than exact guarantees due to event loop call stack blocking.
  2. 02Forgetting to clear an active setInterval loop creates memory leaks that persist throughout application life.

setTimeout and clearTimeout

    setTimeout

    Schedules a single callback execution after a specified millisecond delay.

    setTimeout(() => {
      console.log("Runs after 1 second");
    }, 1000);
    Timer cancellation

    Cancels a scheduled setTimeout callback before execution using its timer ID.

    const id = setTimeout(() => console.log("done"), 5000);
    clearTimeout(id);
    Forwarding arguments

    Passes extra arguments directly into the timer callback function handler.

    setTimeout(u => console.log(u), 1000, "Alice");
    Zero delay

    Queues 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

    setInterval

    Schedules repeated callback execution on a fixed time interval loop.

    const id = setInterval(() => {
      console.log("tick");
    }, 1000);
    Self-clearing interval

    Stops a repeating interval internally once a counter threshold is met.

    let count = 0;
    const id = setInterval(() => {
      count++;
      if (count >= 5) clearInterval(id);
    }, 1000);
    Poller cleanups

    Clears active poll intervals during cleanups to prevent resource leaks.

    function poll() {
      const id = setInterval(fetchData, 5000);
      return () => clearInterval(id); // clean
    }
    Recursive timeout

    Chains setTimeouts recursively to ensure steady spacing between variable runs.

    function poll() {
      doWork();
      setTimeout(poll, 1000);
    }
    poll();

Timer Precision and Event Loop

    Main thread blocks

    Timers await thread clearance, causing late execution if synchronous blocks run.

    setTimeout(() => console.log("late"), 0);
    while (Date.now() < start + 200) {} // block
    Sequential queueing

    Long synchronous execution blocks delay all queued callbacks concurrently.

    setTimeout(() => console.log("A"), 10);
    setTimeout(() => console.log("B"), 20);
    // delayed together if thread is busy
    Macrotask queueing

    Timers 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 helper

    Delays 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 search

    Limits 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 helper

    Executes 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 tracking

    Protects browser scroll listeners from triggering expensive paint redraws.

    const logScroll = throttle(() => updateUI(), 200);
    window.addEventListener("scroll", logScroll);

requestAnimationFrame

    rAF animation loop

    Schedules callbacks right before the browser repaints active screen elements.

    function animate() {
      moveElement();
      requestAnimationFrame(animate);
    }
    requestAnimationFrame(animate);
    Animation stop

    Terminates a requestAnimationFrame animation loop using the returned ID.

    const id = requestAnimationFrame(animate);
    cancelAnimationFrame(id);
    Scroll sync

    Synchronizes visual adjustments directly with browser repaint refresh frames.

    let ticking = false;
    window.addEventListener("scroll", () => {
      if (!ticking) {
        requestAnimationFrame(() => {
          updateScroll();
          ticking = false;
        });
        ticking = true;
      }
    });

In Practice

FAQ