Timers & Observers
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.