Skip to main content

Timers & Observers

intermediate14 min readLesson 69 of 143

Debounce and throttle with setTimeout, IntersectionObserver for lazy work, ResizeObserver, and why setInterval lies about time.

Browsers give you clocks and observers โ€” using them well is what separates a smooth app from a janky one.

Timers are queues, not alarms

setTimeout(fn, 0) does not run "immediately" โ€” it queues fn to run after the current code finishes (the event loop, covered properly in the async module). setInterval(fn, 1000) fires no faster than every second when the main thread is free โ€” a slow handler stretches the real interval. When you need a repeating action, prefer scheduling the next setTimeout after the work completes.

Debounce and throttle

  • Debounce โ€” react when the user pauses: wait for a quiet gap (search-as-you-type).
  • Throttle โ€” react at most once per interval: cap the rate (scroll position reporting).
function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

Both are closure patterns from Module 1 โ€” the timer handle lives in the closure.

Observers: the browser watches for you

Instead of polling, subscribe:

const io = new IntersectionObserver((entries) => {
  for (const e of entries) {
    if (e.isIntersecting) loadMore();
  }
});
io.observe(sentinel);

IntersectionObserver reports visibility (lazy images, infinite scroll); ResizeObserver reports size changes (redraw canvases, collapse widgets). Both are cheaper and more accurate than scroll or resize handlers โ€” and remember prefers-reduced-motion when animations are involved.

Now practice

Timers & Observers โ€” PracticeTime and visibility without jank: debounce vs throttle by signature, lazy-load simulation, and a resize model.2 challenges ยท ยท ~16 min