Engrain by ByteRush

WEB.04.01 Mid-level· 10 minCore — asked often

IntersectionObserver: knowing what is on screen without scroll listeners

Use IntersectionObserver for lazy loading, infinite scroll sentinels and visibility analytics. How root, rootMargin and threshold work, and why its callbacks keep scrolling smooth.

Comes up in: Web fundamentals, Machine coding

Learn#

Pallavi’s product feed calls getBoundingClientRect() on 200 images inside a scroll listener. Scrolling stutters on mid-range phones: the listener runs dozens of times a second and forces layout each time (WEB.01.04).

An intersection observer asks the browser to tell you when an element enters or leaves an area, usually the viewport. The browser computes it during rendering and calls you afterwards:

const io = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    entry.target.src = entry.target.dataset.src; // load the real image
    io.unobserve(entry.target);                  // once is enough
  }
}, { rootMargin: "200px 0px" });
document.querySelectorAll("img[data-src]").forEach((img) => io.observe(img));

For infinite scroll, observe one empty element after the last item, a sentinel. When it comes into view, load the next page.

An infinite feed with a sentinelStep 1 of 4

Page

  • item 1 … item 20
  • sentinel (y = 1,200 px below)

State

    empty

    Network

      empty

      The sentinel, an empty div after the last item, is 1,200 px below the viewport. rootMargin grows the viewport by 300 px at the bottom.

      Read the steps as text
      1. The sentinel, an empty div after the last item, is 1,200 px below the viewport. rootMargin grows the viewport by 300 px at the bottom.
      2. The user scrolls. While scrolling, your JavaScript does nothing at all.
      3. The sentinel enters the grown viewport. After rendering, the browser queues the callback with isIntersecting: true.
      4. The callback loads page 2 and appends it. The sentinel moves down, ready for the next page.

      Predict#

      Predict first

      You call io.observe(el) on an element that is already fully visible. When does the callback first run?

      Interview angle#

      Say it like this — about 30 seconds

      “IntersectionObserver reports when elements cross into or out of a root, usually the viewport, without scroll listeners or geometry reads. The browser computes intersections during rendering and calls back asynchronously, so scrolling stays smooth. rootMargin grows or shrinks the root, so I can load images 200 pixels before they appear. threshold sets which visible fractions trigger callbacks. I use it for lazy loading, infinite-scroll sentinels and impression tracking. For plain images, loading lazy does the same with no JavaScript.”

      Stays on your device. Nothing is uploaded.
      Follow-up How would you lazy-load images without scroll listeners?
      Use loading="lazy" for images, or an IntersectionObserver that swaps data-src into src and then unobserves.
      Follow-up What is rootMargin for?
      It expands or shrinks the root’s box, so callbacks fire before an element is visible (preload) or only once it is well inside.
      Follow-up How do you count an ad as seen?
      threshold 0.5 and a timer: count it once at least half the element has been visible for one second.

      Practice#

      On your own

      Build an infinite-scroll helper around a sentinel. The tests pass in a fake Observer so they can trigger intersections.

      Done when

      • Uses the observer with the given rootMargin, not a scroll listener.
      • Never starts a second loadMore while one is running.
      • Disconnects when there are no more pages, and on destroy().
      Your solution
      Ctrl/⌘ + Enter

      Remember#

      Try to answer each card before you open it. Then add them to your reviews.

      What does rootMargin do?

      Grows or shrinks the root's box used for intersection checks.

      What does threshold: [0, 0.5, 1] mean?

      Call back when 0%, 50% and 100% of the element is visible.

      What is a sentinel in infinite scroll?

      An empty element after the last item; when it is visible, load more.

      Why is IntersectionObserver cheaper than scroll listeners?

      The browser computes it during rendering and calls back asynchronously; no forced layout.

      Go deeper

      Four layers. Stop at the one your interview needs; each ends with a quick check.

      The core ideaEvery round
      root is the area to check against: the viewport by default, or a scrolling element. Root margin grows or shrinks it, like CSS margin. A threshold is the visible fraction that triggers a callback.
      Check yourself: What is the default root?

      The viewport (the top-level document's).

      How it works underneathSenior rounds
      Intersections are computed during the rendering steps of a frame, after layout, and callbacks are queued to run later as a task. Your code never sits on the scrolling path Source: W3C: Intersection Observer.
      Check yourself: When does the browser compute intersections?

      During the rendering steps, after layout; callbacks are queued as tasks.

      Edge cases and trapsWhere people slip

      Each entry gives isIntersecting, intersectionRatio and boundingClientRect from the time of the check. In a cross-origin iframe, rootMargin is ignored when the root is the viewport. A zero-size element can still intersect.

      Check yourself: Why might rootMargin seem to do nothing inside a cross-origin iframe?

      With the viewport as root, rootMargin is ignored there for privacy.

      In productionStaff-level answers

      For impression analytics, combine a threshold with a timer.
      Prefer loading="lazy" for images below the fold, never for the LCP image Source: MDN: Intersection Observer API.

      Check yourself: Which needs no JavaScript at all for lazy images?

      The loading="lazy" attribute on img and iframe.

      Finished the five steps? Mark it, and its cards join your reviews.

      Last checked 2026-11-01. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

      Interview questions this answers

      • How would you implement lazy loading of images without scroll listeners?
      • What is IntersectionObserver's rootMargin for?