HuntExams Academy logo
ReactAdvanced#performance

What is lazy loading and code splitting in React?

Answer

React.lazy plus Suspense loads a component's bundle only when it renders, shrinking the initial JavaScript payload. Route-level splitting gives the biggest win.

Example

const Reports = React.lazy(() => import('./Reports'));
<Suspense fallback={<Spinner />}>
  <Reports />
</Suspense>

Detailed explanation

Code splitting divides an application's JavaScript into smaller chunks rather than shipping every feature in one initial bundle. Lazy loading defers requesting or using those chunks until they are needed. In React applications, this often means loading a page, modal, or expensive widget only when the user reaches it. This can reduce initial download, parsing, and execution work, helping the first screen become usable sooner. However, it introduces later network requests, so splitting is a trade-off rather than an automatic performance improvement.

React.lazy connects a dynamically imported component to React's rendering system. For example, declare const Reports = React.lazy(() => import('./Reports')); at module scope, then render <Suspense fallback={<Spinner />}><Reports /></Suspense>. A supported build tool recognises import() as a split point and typically creates a separate chunk, potentially with shared dependency chunks. React calls the loader when it first attempts to render Reports, not when the lazy declaration executes. The loader returns a promise resolving to a module whose default export is a valid React component. React caches that promise and its resolved value for subsequent renders.

While the component's code is unavailable, it suspends rendering, and the nearest Suspense boundary displays its fallback. Once the import resolves, React retries rendering the component. Suspense coordinates the loading UI; it does not itself split bundles or handle rejected imports as an error screen. If a chunk fails to load, an Error Boundary can show a recovery message. Boundary placement matters: wrapping the entire application can replace a large section of the interface, whereas a local boundary can keep navigation and other useful content visible.

Route-level splitting is often a strong starting point because users do not need every page immediately, and pages commonly contain substantial dependencies. Component-level splitting can help for rarely opened editors or charts, but splitting tiny components can add request overhead and loading flicker. Interviewers look for a clear distinction between bundler behaviour, deferred loading, and React's fallback handling. Mention measuring bundle size and loading performance, avoiding unnecessary request waterfalls, and selectively preloading likely next screens instead of assuming every split improves the experience.

Key points to remember

  • Code splitting creates separate JavaScript chunks; lazy loading delays loading them until needed.
  • React.lazy uses a promise-based loader, commonly dynamic import(), whose resolved module must provide a default component export.
  • Declare lazy components at module scope so their component identity remains stable across renders.
  • Suspense displays a loading fallback; an Error Boundary handles rejected imports.
  • Start with substantial route or feature boundaries, then measure initial-load improvements and navigation delays.

Common mistakes candidates make

  • Claiming Suspense creates bundles or automatically lazy-loads every component it wraps.
  • Declaring React.lazy inside a component, recreating the lazy component type and potentially resetting child state.
  • Splitting every small component without considering extra requests, loading waterfalls, or fallback flicker.

Likely follow-up questions

Can React.lazy load a named export?

Yes, by adapting the imported module into the shape React.lazy expects: const Reports = React.lazy(() => import('./Reports').then(module => ({ default: module.Reports })));. The resulting default property must contain a valid React component.

What happens if the component's chunk fails to download?

React throws the loader's rejection reason to the nearest Error Boundary, which can render an error UI. A retry strategy must account for React.lazy caching its loader promise; simply rerendering the same lazy component is not a reliable retry.

How can you reduce the loading delay when navigating to a lazy-loaded route?

Use router or framework preloading facilities to fetch likely next routes before navigation, such as on link hover or focus. Preloading everything defeats much of the benefit, so base it on likely user intent and measured performance.

Interview tip: Explain splitting versus deferred loading, walk through the Reports example, and finish with boundary placement and one performance trade-off.

Related Interview Questions

1
ReactAdvanced#performance#data

What is Suspense?

Open
2
ReactAdvanced#patterns

What are higher-order components (HOCs)?

Open
3
ReactAdvanced#patterns#dom

What are React portals?

Open