Lazy Loading
Lazy loading postpones downloading or executing code until it is actually needed. In frontend applications, it can reduce the initial JavaScript payload and improve startup performance.
Lazy loading is a bundle-optimization technique that defers resources or modules until they are required. Instead of placing every feature in the initial bundle, an application can load some code later when a user reaches the relevant feature or route.
In JavaScript, dynamic `import()` is a common mechanism for lazy-loading modules. Bundlers can use these dynamic imports as split points and create separate chunks that are fetched when the import executes.
Route-based lazy loading is especially common in frontend applications. For example, the code for an account-settings page does not need to be downloaded while a user is viewing the home page if navigation to settings can trigger the load later.
Lazy loading can improve initial load time, but it does not make the deferred code free. The application still needs to download and execute that code when it becomes necessary, which can introduce a delay at that moment. Prefetching or preloading can sometimes reduce that later delay when future usage is predictable.
Key points
- Defer non-critical code until it is needed.
- Dynamic `import()` can create a bundle-splitting boundary.
- Route-level lazy loading can reduce the initial JavaScript payload.
- Deferred code still costs network and execution time when eventually used.
- Use lazy loading selectively; critical above-the-fold functionality may be better loaded immediately.
Interview questions
What is lazy loading, and why would you use it in a frontend application?
Lazy loading means delaying the loading or execution of a resource until the application needs it. For JavaScript, this can reduce the amount of code required during the initial page load. That can improve startup performance, especially when an application contains large features that many users do not immediately visit. The trade-off is that the deferred feature may take time to load when the user first opens it.
How does dynamic `import()` enable lazy loading?
Dynamic `import()` loads a JavaScript module asynchronously instead of making it part of the normal static import path. A bundler can recognize the dynamic import as a boundary and emit a separately loadable chunk. The chunk is requested when the dynamic import executes. The returned promise resolves to the module's exports.
Where would you typically apply lazy loading?
A common choice is route-level code, where features that correspond to less frequently visited routes are loaded only after navigation. Large optional components, editors, charts, or administrative features can also be good candidates. I would avoid delaying code that is required immediately for the primary user experience. The decision should consider both initial-load savings and the latency introduced when the feature is first requested.
What are the trade-offs of lazy loading?
The main benefit is a smaller initial payload and less work during startup. The main cost is that users may encounter a loading delay when they first request deferred functionality. Excessive splitting can also create additional network and runtime overhead. A good implementation balances initial performance against the latency and complexity of later loads.
How would you handle loading and failure states for a lazy-loaded feature?
The UI should represent the asynchronous state while the module is being fetched, such as with a loading placeholder or skeleton. It should also handle import failures so the application can display a useful fallback rather than leaving the interface broken. Depending on the framework, this may be implemented with suspense-like boundaries, error boundaries, or explicit promise state. I would also consider retry behavior and whether the resource can be prefetched when navigation is predictable.