Lazy loading is a web performance technique that defers the loading of resources until they are needed, typically until an image or iframe approaches the viewport or until a feature is actually used. Instead of downloading everything at page load, the browser fetches only what the first view requires and postpones the rest.
Applied well, lazy loading reduces initial bytes, network contention and main-thread work, which helps metrics such as Largest Contentful Paint and Total Blocking Time. Applied to the wrong element, especially the main above-the-fold image, it delays exactly the content the user is waiting for.
How lazy loading defers resources
Lazy loading replaces an eager request with a deferred request triggered by a condition. For media, the condition is usually distance from the viewport; for code, it is a route change or a user action. The browser or a script watches for that condition and only then starts the network request.
With native lazy loading, the developer adds loading="lazy" to an img or iframe element. The browser keeps the element in the DOM, skips the fetch, and starts it once the element comes within a browser-defined distance threshold of the viewport. Chromium-based browsers use thresholds of roughly 1,250 to 2,500 pixels, depending on connection speed, so images usually arrive before they scroll into view.
Script-based lazy loading relies on the Intersection Observer API. A placeholder carries the real address in a data-src attribute, an observer with a rootMargin such as 200px reports when it nears the viewport, and the script then swaps in the real source. This approach still matters for background images, custom components and fine-grained control.
Types of lazy loading and what they defer
Lazy loading is a family of techniques, not one attribute. Each variant defers a different kind of resource and needs a different trigger.
| Form | Resource deferred | Typical trigger | Implementation |
|---|---|---|---|
| Native image lazy loading | Below-the-fold images | Distance from viewport | loading="lazy" on img |
| Iframe lazy loading | Embedded videos, maps, widgets | Distance from viewport | loading="lazy" on iframe |
| Observer-based loading | Background images, custom elements | Intersection with root margin | Intersection Observer API |
| Code lazy loading | JavaScript modules and components | Route change or interaction | Dynamic import() with code splitting |
| Facade pattern | Heavy third-party embeds | Click on a lightweight preview | Static image replaced on demand |
| Data lazy loading | List items, comments, feeds | Scroll position or pagination | Fetch on demand |
Code lazy loading is closely tied to code splitting: a module bundler splits the application into chunks, and the dynamic import() call loads a chunk only when a route or dialog needs it. The facade pattern often gives the largest savings, because a single video embed can pull in several hundred kilobytes of script before the user presses play.
Effects on Core Web Vitals and loading metrics
Lazy loading changes what competes for bandwidth and CPU in the first seconds. Its effect on each metric depends on whether deferred resources belong to the first view.
| Metric | Effect when applied correctly | Effect when misapplied |
|---|---|---|
| LCP | Improves by freeing bandwidth for the hero element | Worsens if the LCP image is lazy |
| CLS | Neutral when dimensions are reserved | Worsens if images lack width and height |
| INP and TBT | Improves when deferred code is never parsed early | Worsens if large chunks load on first click |
| Total page weight | Falls for users who never scroll | Unchanged for users who read everything |
| Speed Index | Usually improves through faster first view | Worsens with visible blank placeholders |
The most frequent regression is a lazily loaded LCP image. The browser cannot start the request until layout confirms the image position, which typically adds hundreds of milliseconds compared with an eager request discovered by the preload scanner. Hero images should stay eager and may receive fetchpriority="high" or a preload hint.
Lazy loading compared with preload, prefetch and eager loading
Lazy loading is one of several resource prioritization strategies. It lowers priority by postponing a request, while preload and prefetch move requests earlier.
| Strategy | Timing of the request | Best suited to |
|---|---|---|
| Eager loading | As soon as the parser discovers it | Content in the first view |
| Lazy loading | When needed or near the viewport | Below-the-fold media and optional features |
| Preload | Early, at high priority | Critical late-discovered assets such as fonts |
| Prefetch | During idle time, low priority | Resources for a likely next navigation |
These strategies are complementary. A well-tuned page preloads its critical assets, eagerly loads the first view, lazy loads the rest and prefetches the next likely page during idle time.
Implementation choices and common mistakes
A safe default is to lazy load everything below the first viewport and nothing within it. Because the first viewport differs between phones and desktops, many teams keep the first two or three images eager and lazy load the remainder of long lists.
| Mistake | Symptom | Fix |
|---|---|---|
| Lazy hero image | Slow LCP, late visible content | Load it eagerly with high fetch priority |
| Missing dimensions | Content jumps as images arrive | Set width and height or aspect-ratio |
| Scroll event listeners | Janky scrolling, extra main-thread work | Use native loading or Intersection Observer |
| Lazy content hidden from crawlers | Text or images missing from search indexes | Keep real elements in HTML, avoid scroll-only triggers |
| Too small root margin | Blank images during fast scrolling | Load earlier with a larger margin |
Search engines render pages but do not scroll like users, so content that loads only after scroll or interaction may never be seen. Native attributes and observer-based loading of real elements are crawler-friendly; content injected after infinite scrolling needs paginated fallback URLs.
A plain-language summary
Imagine a restaurant that cooks every dish on the menu the moment a guest sits down. The kitchen would be overloaded and the first plate would arrive late. A sensible restaurant cooks each dish when it is ordered.
Lazy loading applies the same idea to web pages. The browser first fetches what is visible on the screen and waits to fetch the pictures, videos and features further down until the reader is about to reach them. The page appears faster, and visitors who never scroll never pay for content they did not see.
An example from practice
An online furniture store has category pages with 48 product photos and an embedded video guide near the bottom. Every photo is about 120 KB, so a phone on a mobile network downloads nearly 6 MB of images before the page settles, and the hero banner competes with all of them for bandwidth.
The team keeps the banner and the first four product photos eager, gives the banner high fetch priority, adds loading="lazy" with explicit dimensions to the remaining photos, and replaces the video with a click-to-play facade. Initial transfer drops to under 1 MB, the measured LCP on mid-range phones improves by roughly one second, and layout shift stays near zero because every image reserves its space.