Implement IntersectionObserver in TypeScript for lazy-loading images

Tests precise DOM typing and observer lifecycle. A strong answer types the callback as receiving IntersectionObserverEntry[], narrows entry.target to HTMLImageElement, swaps data-src to src, and calls unobserve.
What's really being asked
This question tests three senior-level skills: precise TypeScript typing of browser APIs, understanding of structural DOM types, and memory-conscious lifecycle management. The interviewer wants to see that you know IntersectionObserverEntry is the correct type for the callback payload, that entry.target is typed as Element rather than HTMLElement, and that you understand why explicit typing matters for maintainability and refactoring. They are also checking whether you know the difference between the observer instance and the entry objects it emits.
The full answer
First, declare the callback with the exact signature the browser expects: a function taking entries as IntersectionObserverEntry[] and the observer instance as IntersectionObserver, returning void. Second, inside the callback, loop over entries and check entry.isIntersecting before acting. Third, narrow entry.target from Element to HTMLImageElement using instanceof or a type guard before touching image-specific properties like src or dataset.src. Fourth, after swapping the data-src attribute into src, call observer.unobserve(entry.target) so the observer stops watching that node and frees up resources. Fifth, optionally store the observer in a ref or variable so you can call disconnect when the component unmounts or the page changes. Mentioning that root and rootMargin are also configurable properties on the observer options object shows deeper familiarity with the API.
The mistakes people make
A major red flag is typing the callback arguments as any or relying entirely on implicit any, which erases safety on entry.target. Another mistake is assuming entry.target is already an HTMLElement or HTMLImageElement without narrowing, because TypeScript will error if you try to assign to src on a generic Element. Some candidates forget to call unobserve after the image loads, which keeps the observer alive and can trigger unnecessary callbacks. Finally, typing entries as a single IntersectionObserverEntry instead of an array shows confusion about the API contract. Equally bad is creating a new observer for every image rather than reusing one instance to watch multiple targets.
What usually comes next
The interviewer may ask how you would handle responsive images with srcset and sizes, which requires typing the target as HTMLImageElement and mutating those specific properties safely. They might ask about rootMargin for preloading images before they enter the viewport, or how to polyfill or gracefully degrade for older browsers. Another follow-up is testing performance: why not use scroll events? The answer is that IntersectionObserver runs off the main thread and avoids layout thrashing. You might also be asked how to test this in a headless environment where layout calculations behave differently.
A concrete example
const imgObserver = new IntersectionObserver((entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target as HTMLImageElement; if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute('data-src'); observer.unobserve(img); } } }); }, { rootMargin: '50px', threshold: 0.01 }); document.querySelectorAll('img[data-src]').forEach(img => imgObserver.observe(img));
Interview question
Which pattern correctly implements an IntersectionObserver callback in TypeScript for lazy-loading images while preserving type safety and memory efficiency?
- a.Type entries as IntersectionObserverEntry[], narrow entry.target with instanceof HTMLImageElement, and call observer.unobserve(entry.target) after setting srcCorrect
- b.Type entries as IntersectionObserverEntry, assert entry.target to HTMLImageElement, and call observer.disconnect() after each image loads
- c.Type entries as IntersectionObserverEntry[], treat entry.target as HTMLElement, and call observer.disconnect() once per callback invocation
- d.Type entries as any[], mutate entry.target.src directly, and instantiate one IntersectionObserver per image
Why? this is the answer
The callback receives IntersectionObserverEntry[], and because entry.target is typed as Element, you must narrow it to HTMLImageElement before setting src and then call observer.unobserve(entry.target) to release that node. Option C is tempting because the array type is correct, but HTMLElement is still too broad for src and disconnect stops all observations instead of just the loaded image.
Just read this? Test yourself on what you have been reading.
Read the original → developer.mozilla.org
- #typescript
- #web apis
- #intersectionobserver
- #dom
- #performance
You just looked this up. Could you explain it out loud?
That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on typescript — each one lists the topics its interview covers.
See open roles