Under The Hood
frontend javascript IntersectionObserver API (Level 2)

IntersectionObserver: Async Off-Main-Thread Threshold Checks

Last updated
Prerequisites:
DOM and viewport concepts
Understanding of scroll events and layout
  • intersection-observer
  • scroll
  • performance
  • lazy-loading
  • observers
  • viewport

Read at your depth

The practical view

IntersectionObserver reports when an element intersects the viewport (or another root element) — useful for lazy-loading images, infinite scroll, and read-progress indicators. const io = new IntersectionObserver((entries) => { entries.forEach(e => { if (e.isIntersecting) load(e.target); }); }, { threshold: 0.25 }); io.observe(img);. The callback runs asynchronously when the observed element crosses configured thresholds (0 to 1, or an array like [0, 0.5, 1]). Unobserve/disconnect when done. Compared to scroll + getBoundingClientRect, this runs off the hot scroll path and is far cheaper.

The same idea in other frameworks

react equivalent
react-intersection-observer wraps the API for hooks; React apps use it for lazy images and infinite lists.

Legacy vs modern

Scroll + getBoundingClientRect vs IntersectionObserver for visibility

Reading geometry on every scroll forces synchronous layout and janks; IntersectionObserver computes intersection asynchronously and fires only on threshold crossings.

before → after
Scroll listener + getBoundingClientRect
window.addEventListener('scroll', () => { const r = el.getBoundingClientRect(); if (r.top < innerHeight) load(); }); // ❌ OLD: layout read per scroll frame
IntersectionObserver
const io = new IntersectionObserver(entries => { if (entries[0].isIntersecting) load(); });
io.observe(el); // ✅ NEW: async, engine-computed, threshold-based

Interview gotchas

Context

This is the classic performance question — interviewers want the layout-thrash and off-main-thread mechanics, not just 'it's the modern API'.

The mechanical answer

A scroll listener runs on the main thread at scroll frequency; every getBoundingClientRect call inside forces a synchronous layout pass to return up-to-date geometry, which can invalidate and re-flow the page mid-frame — the leading cause of scroll jank. IntersectionObserver moves the intersection computation into the engine's own asynchronous rendering/compositor pipeline: after a scroll or layout update, the engine checks observed targets against the root box and only queues a callback when a threshold is crossed. This avoids per-scroll layout reads, is coalesced, and scales to many observed elements without binding main-thread work to scroll frequency.

Trap

The naive answer is 'getBoundingClientRect is accurate but IntersectionObserver is approximate'. Both are accurate; the difference is synchronous-layout cost vs async engine computation. Another trap: assuming the IO callback fires on every scroll position change — it fires only when the intersection ratio crosses a configured threshold, so code that expects per-frame updates must use appropriate thresholds or fall back to rAF-driven measurement for pixel-exact animations.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.