JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeTopicsperformanceLazy Loading Techniques
PrevNext
performance
beginner
15 min read

Lazy Loading Techniques

code-splitting
dynamic-import
intersection-observer
lazy-loading
next-dynamic
react-lazy
suspense

Lazy loading defers the loading of non-critical resources until they are needed, reducing initial page weight and improving Time to Interactive through native attributes, Intersection Observer, and dynamic imports.

Key Points

1Native Loading Attribute

The loading="lazy" attribute on img and iframe elements defers loading with zero JavaScript, supported in all modern browsers.

2Intersection Observer

An efficient browser API for detecting when elements enter the viewport, replacing scroll listeners for custom lazy loading behavior.

3Dynamic import()

The import() expression loads JavaScript modules on demand and enables bundlers to create separate chunks for code splitting.

4React.lazy with Suspense

React's built-in component-level lazy loading wraps dynamic imports with declarative fallback UI during loading.

What You'll Learn

  • Use native loading="lazy" correctly and know when not to apply it (above-the-fold content)
  • Implement custom lazy loading with Intersection Observer for non-image resources
  • Apply dynamic import() for on-demand module loading and code splitting
  • Use React.lazy and Suspense to defer rendering of heavy components

Deep Dive

Lazy loading is a design pattern that delays the initialization or fetching of resources until they are actually needed. Instead of loading everything upfront, you load only what is required for the initial view and defer the rest. This directly improves initial page load time, reduces bandwidth consumption, and lowers memory usage.

Native Browser Lazy Loading

The simplest approach uses the loading attribute on <img> and <iframe> elements:

HTML
<img src="photo.webp" loading="lazy" alt="Description" />
<iframe src="video.html" loading="lazy"></iframe>

The browser automatically defers loading until the element is near the viewport. This requires zero JavaScript, works in all modern browsers, and is the recommended approach for images and iframes below the fold. The browser uses heuristics to determine "near the viewport" — typically starting the fetch when the element is within a few viewport heights of the visible area.

Important: Never lazy-load above-the-fold content. Images visible on initial load should use loading="eager" (the default) and consider fetchpriority="high" for the Largest Contentful Paint (LCP) element.

Intersection Observer API

For custom lazy loading behavior beyond images, the Intersection Observer API provides an efficient way to detect when elements enter the viewport:

JavaScript
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadContent(entry.target);
      observer.unobserve(entry.target);
    }
  });
}, { rootMargin: '200px' });

This is more performant than scroll event listeners because the browser batches observations and runs them off the main thread. Use it for lazy-loading components, triggering animations on scroll, implementing infinite scroll, or tracking element visibility for analytics.

Dynamic import() for JavaScript Modules

The import() expression loads JavaScript modules on demand, returning a promise:

JavaScript
button.addEventListener('click', async () => {
  const { heavyFunction } = await import('./heavyModule.js');
  heavyFunction();
});

Bundlers like Webpack and Vite recognize import() and automatically create separate chunks for dynamically imported modules. This is the foundation of code splitting — routes, features, or large dependencies can be loaded only when the user needs them.

React.lazy and Suspense

React provides built-in component-level lazy loading:

JSX
const HeavyChart = React.lazy(() => import('./HeavyChart'));
 
function Dashboard() {
  return (
    <Suspense fallback={<Skeleton />}>
      <HeavyChart />
    </Suspense>
  );
}

React.lazy wraps a dynamic import and returns a component that renders the imported module's default export. Suspense provides a fallback UI while the component is loading. In Next.js, next/dynamic extends this with SSR control and custom loading states.

When to Lazy Load

Lazy load content below the fold (images, videos, iframes), heavy third-party libraries (chart libraries, rich text editors, maps), routes the user has not visited yet, and features behind user interaction (modals, dropdowns, tooltips). Do not lazy load critical above-the-fold content, small modules where the loading overhead exceeds the savings, or content needed immediately after page load.

Key Interview Distinction

Native loading="lazy" is for images and iframes. Intersection Observer is for custom viewport-triggered behavior. import() is for JavaScript modules and code splitting. React.lazy is for component-level splitting with Suspense fallbacks. Each technique targets a different type of resource.

Fun Fact

The Intersection Observer API was designed by Google engineers who found that scroll-based lazy loading libraries were themselves causing performance problems. Thousands of websites were attaching expensive scroll event listeners to implement lazy loading, ironically degrading the performance they were trying to improve.

Continue Learning

Bundling & Code Splitting

intermediate

Image Optimization

beginner

Core Web Vitals & Performance Metrics

beginner

Practice What You Learned

What is lazy loading and how do you implement it?
junior
loading
Lazy loading defers loading non-critical resources until they're needed. For images, use loading="lazy" attribute. For JavaScript, use dynamic import() or React.lazy(). This reduces initial page load time and saves bandwidth.
Previous
Image Optimization
Next
Memory Leaks & Management
PrevNext