Lesson 6 of 14
loading="lazy" and <picture> replaced a decade of IntersectionObserver libraries with two HTML attributes — but applying loading="lazy" to the wrong image is one of the most common real Core Web Vitals regressions, and image discovery for search is a separate mechanism entirely.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Before 2019, deferring offscreen images until they scrolled into view meant a JavaScript library watching scroll position (or, if you were fancier, an IntersectionObserver) and swapping a placeholder src for the real one. Native lazy loading collapses that into a single HTML attribute:
loading="lazy" on <img> or <iframe> tells the browser to defer fetching that resource until it's near the viewport — no JS, no library, no placeholder swap.decoding="async" tells the browser it's allowed to decode the image off the main thread, so a large image finishing its fetch doesn't block rendering of text and other content that's already ready. It's independent of loading — you can use either without the other, but they're commonly paired.<picture> with srcset/sizes solves a different problem: not when to load an image, but which image to load. srcset on a <source> or <img> lists candidate files at different resolutions; sizes tells the browser how much layout space the image will occupy at different viewport widths, so it can pick the smallest candidate that's still sharp enough — without JS, without you hardcoding breakpoints in a component. <picture> additionally allows art direction: serving a genuinely different crop or image entirely at different breakpoints (a tight portrait crop on mobile, a wide landscape on desktop), not just a smaller version of the same file.The mistake that makes this section belong in an SEO/performance track and not just a "modern HTML" one: loading="lazy" on the actual LCP image directly damages your Largest Contentful Paint score. The browser's LCP candidate is, by definition, the largest visible content element in the initial viewport — and lazy-loading is a signal to the browser that a resource is not urgent, deferring its fetch until layout confirms it's near-viewport. Applying "not urgent" to the one image Core Web Vitals is timing is a direct, self-inflicted regression, and it's a common one because "lazy-load your images" is repeated as blanket advice without the LCP carve-out.
<!-- Hero/LCP image: eager, no lazy-loading, high fetch priority -->
<img
src="/hero-product.avif"
alt="Featured product"
fetchpriority="high"
width="800"
height="600"
/>
<!-- Below-the-fold product grid: lazy-loaded, async-decoded -->
<picture>
<source
media="(max-width: 600px)"
srcset="/product-2-mobile.avif 480w, /product-2-mobile-2x.avif 960w"
sizes="100vw"
/>
<source
srcset="/product-2.avif 400w, /product-2-2x.avif 800w"
sizes="25vw"
/>
<img
src="/product-2.avif"
alt="Wireless keyboard, side profile"
loading="lazy"
decoding="async"
width="400"
height="300"
/>
</picture>
Only the below-the-fold grid images get loading="lazy". The hero image — the LCP candidate on this page — is loaded eagerly (the default; loading="eager" doesn't need to be written) and gets fetchpriority="high" instead, the opposite treatment.
Lazy-loading and responsive images control page performance — they say nothing about whether Google can find your images at all. That's what an image sitemap is for: an <image:image> extension inside your regular XML sitemap (or a dedicated image sitemap) that explicitly lists image URLs per page. It matters most when images are surfaced in ways a crawler might not reliably discover on its own — loaded via JavaScript after initial render, referenced only as CSS background-image, or served from a CDN domain different from the page's own. For a typical <img src> with real alt text, Google generally finds it fine during normal crawling; image sitemaps are a discovery aid for the harder cases, not a ranking signal, and not a replacement for descriptive alt text and filenames.
loading="lazy" and decoding="async" solve different problems and are independent attributes — one defers the fetch, the other moves decode work off the main thread. Using one doesn't imply the other.srcset/sizes picks a resolution; <picture> with multiple <source> elements enables art direction — a genuinely different image per breakpoint, not just a smaller file. Conflating the two leads to reaching for <picture> when plain srcset on an <img> would do.<iframe>s are the same LCP/INP trap as images, but less obviously — an embedded video near the top of the viewport, lazy-loaded reflexively, delays exactly what CWV is measuring.A candidate who says "always lazy-load images for performance" without the LCP carve-out is giving advice that's actively wrong for the one image that determines the page's headline Core Web Vitals score — interviewers probing real production experience listen specifically for that exception. Being able to also separate "how fast do images load" (lazy-loading, srcset) from "can Google find my images at all" (image sitemaps) signals the candidate isn't treating SEO and performance as the same lever, which is a common shallow-answer tell.