Back
Web Fundamentals

Import on Visibility: Lazy Loading with IntersectionObserver

Web Fundamentals
Build & Deployment: Monorepo, CI/CD, Strategies & Release SafetyState Management: Choosing the Right SolutionRedux: Predictable State Container (RTK + RTK Query)React Query (TanStack Query): Server State CachingData Fetching Patterns: REST, GraphQL, tRPC & Real-timeGraphQL Fundamentals for Frontend: Shape, Caching, and TradeoffsgRPC-Web Fundamentals: Browser Constraints and Proxy ModelCaching Strategies: Client, Server & EdgeData Normalization: Organizing State for PerformanceAPI Design Best Practices: Pagination, Errors, Versioning & Type SafetyAPI Versioning Strategies for Frontend CompatibilityPagination: Offset vs Cursor-BasedRate Limiting & API Resilience: Retries, Backoff, Jitter, IdempotencyHow Frontend Developers Can Handle Millions of API Requests Without Crashing EverythingBrowser Storage: Cookies, SessionStorage, LocalStorage, IndexedDBReal-time Communication: WebSockets, SSE & PollingWebRTC: Real-Time Communication in the BrowserCore Web Vitals: LCP, INP & CLSPerformance Optimization Trade-offsCritical Resource Prioritization: Optimize Loading OrderCode Splitting: Optimize Bundle Size with Dynamic ImportsTree Shaking: Eliminate Dead Code from Your BundleLazy Loading: Load Resources On-DemandResource Hints: Preload, Prefetch & PreconnectText Compression: Gzip and BrotliImage & Video Optimization: Modern Formats & TechniquesAdaptive Loading: Optimize for Device & NetworkList Virtualization: Render Large Lists EfficientlyWeb Workers vs Main Thread: Offloading Heavy WorkMemory Leaks in Frontend Apps: Detection & PreventionManaging Third-Party Scripts: Optimization StrategiesHow CDNs Work: Edge Delivery, Caching & PerformanceHTTP Caching Deep Dive: Cache-Control, ETag & RevalidationService Workers & Offline Strategy: Cache First, Network First & Update LifecyclePWA Fundamentals: Manifest, Installability & Offline UXCritical Rendering PathScript Loading: async vs deferEvent Loop: Understanding JavaScript Execution ModelJavaScript Module Systems: CJS vs ESM vs UMDDynamic Module Loading: import() FunctionImport on Interaction: Load When User InteractsImport on Visibility: Lazy Loading with IntersectionObserverBrowser Rendering Pipeline & Layout ThrashingRendering Strategies: CSR vs SSR vs SSG vs ISRStreaming SSR: Progressive HTML StreamingIslands Architecture: Independent Component HydrationReact Server Components: Zero-JS Server RenderingFramework Reactivity: React, Vue, Svelte & SolidHTTP/1.1 vs HTTP/2 vs HTTP/3 (QUIC) for Frontend PerformanceDNS Resolution: Path, TTL, Caching & Frontend ImpactCross-Site Scripting (XSS) AttacksCross-Site Request Forgery (CSRF) AttacksCORS Explained: Cross-Origin Resource SharingCORS Preflight in Practice: Credentials, Simple Requests & MisconfigurationsContent Security Policy (CSP)Why is HTTPS Secure? Understanding TLS/SSLAuthorization Best PracticesCookie Security & Session Hardening: SameSite, HttpOnly, Secure
mediumRendering & Browser Architecture

Import on Visibility: Lazy Loading with IntersectionObserver

TL;DRUse IntersectionObserver + dynamic import() to load heavy components only when they enter the viewport
High Signal
Google
Meta
Agoda
Meesho
30-Second Answerstart every interview with this

Import on Visibility combines IntersectionObserver with dynamic import() to load JavaScript-heavy components only when they approach the user's viewport. This pattern further reduces initial bundle size for below-the-fold content while providing smooth perceived performance through rootMargin prefetching.

Static imports = cooking everything before opening the restaurant. Import on Interaction = cooking after they order. Import on Visibility = starting prep when you see them walking toward the table (via IntersectionObserver). You save resources on startup and still deliver smoothly when they arrive.

Placeholder renders (lightweight)
IntersectionObserver detects viewport approach
dynamic import() + rootMargin prefetch
Component mounts with loading UI

1IntersectionObserver Basics

The modern, efficient way to detect when an element enters the viewport without expensive scroll listeners. Use rootMargin to start loading slightly before the element is visible.

observer.jsjs
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadHeavyComponent();
      observer.disconnect();
    }
  });
}, { rootMargin: '150px' });

observer.observe(placeholderElement);

2Basic Implementation

Combine observer with dynamic import() to load and mount the real component only when needed.

3React Hook Pattern

Encapsulate logic into a reusable hook for clean, production-ready usage with proper cleanup and state management.

useLoadOnVisibility.jsjs
function useLoadOnVisibility(loader, rootMargin = '150px') {
  const ref = useRef(null);
  const [Component, setComponent] = useState(null);
  // ... loading & error states
}

4Reusable Component & Advanced Tips

Create <LazyLoadOnVisible /> wrapper. Support parallel imports, meaningful placeholders, error handling, and cleanup to prevent memory leaks.

PropertyImport on Mount (useEffect)Import on InteractionImport on Visibility
BehaviorLoads as soon as component mountsLoads on click/hover/focusLoads when near viewport
Use CaseContent needed soon after renderUser-triggered features (modals, editors)Below-the-fold heavy widgets
PerformanceEarlier but less optimalBest for explicit intentBalances delay with smoothness
Bundle ImpactStill affects initial load if above foldVery small initial bundleSmall initial bundle

Import on Mount (useEffect)

Behavior

Loads as soon as component mounts

Use Case

Content needed soon after render

Performance

Earlier but less optimal

Bundle Impact

Still affects initial load if above fold

Import on Interaction

Behavior

Loads on click/hover/focus

Use Case

User-triggered features (modals, editors)

Performance

Best for explicit intent

Bundle Impact

Very small initial bundle

Import on Visibility

Behavior

Loads when near viewport

Use Case

Below-the-fold heavy widgets

Performance

Balances delay with smoothness

Bundle Impact

Small initial bundle

Common questions

  • ›“How would you lazy load a component only when it scrolls into view?”
  • ›“Compare import on interaction vs import on visibility.”
  • ›“How do you implement this pattern in React?”
  • ›“What are the trade-offs and best practices?”

What interviewers look for

  • Correct use of IntersectionObserver + dynamic import()
  • Understanding of rootMargin for prefetching
  • Proper cleanup and single-load guarantees
  • Awareness of good vs bad candidates and UX considerations

Short answer (60 sec)

Use IntersectionObserver to watch a placeholder. When it enters the viewport (with rootMargin), trigger dynamic import() to load the heavy component. This keeps the initial bundle small while loading below-the-fold content just in time.

Detailed answer (senior level)

This pattern is ideal for comments, embeds, charts, and other below-the-fold heavy UI. rootMargin (e.g. 150px) starts loading early for smoother experience. Always disconnect the observer after loading, cache the module, and provide meaningful placeholders + error states. It complements import on interaction and React.lazy for comprehensive lazy loading strategy.

  • Forgetting to disconnect the observer (memory leaks)
  • Using scroll listeners instead of IntersectionObserver
  • Loading critical/above-the-fold content this way
  • No loading states or poor placeholders
  • Not handling errors or multiple triggers
Key Takeaways
  • ✓IntersectionObserver + dynamic import() = efficient below-the-fold lazy loading
  • ✓Use positive rootMargin to prefetch before visibility
  • ✓Always disconnect observer and guard against duplicate loads
  • ✓Best for comments, embeds, charts, recommendations — not core UI
  • ✓Provide excellent placeholders and error handling for great UX
  • ✓Combine with import on interaction for complete lazy loading coverage
Previous TopicImport on Interaction: Load When User InteractsNext Topic Browser Rendering Pipeline & Layout Thrashing

On this page