Performance & Optimization
Lazy Loading & Code Splitting
Master page speed optimizations in JavaScript. Learn static vs dynamic imports, code splitting bundles, and lazy loading images with IntersectionObserver.
1. Introduction
Lazy Loading and Code Splitting are performance optimization techniques used to speed up initial page load times. Code Splitting divides your application code into smaller chunks that can be loaded on demand. Lazy Loading delays loading resources (like scripts or images) until they are actually needed (e.g. when an element scrolls into view).
2. Why It Matters
If your application compiles all code into a single massive JavaScript bundle, users must download and parse the entire file before they can interact with the page. This is inefficient for users on slow mobile networks. Splitting bundles allows users to load only the code required for the current page, reducing startup times.
3. Real-World Analogy
Think of a Multi-Course Banquet Dinner:
- Monolithic Bundle (All food at once): The restaurant places the appetizer, soup, salad, steak, dessert, and coffee on the table at the same time. The food gets cold, the table is cluttered, and you are overwhelmed.
- Code Splitting & Lazy Loading (Course-by-Course service): The waiter brings out the appetizer first (initial bundle). When you finish the appetizer, the waiter fetches the soup (lazy loading on demand). The kitchen prepares dessert only when you request it. You save table space, and the food is served fresh.
4. Code Splitting via Dynamic Imports
Standard ES imports are static (loaded immediately). You can split code using dynamic imports (import()), which load modules asynchronously:
5. Lazy Loading Images
You can lazy load images using the native loading="lazy" attribute, or implement custom lazy loading using the IntersectionObserver API:
6. Practical Example
This script demonstrates lazy loading a script or library when the user scrolls to a specific component container on the page:
7. Common Mistakes
- Lazy loading above-the-fold content: Avoid lazy loading elements visible in the viewport on initial load (like header logos or hero banner images). Lazy loading above-the-fold content delays rendering, worsening your Largest Contentful Paint (LCP) web vital score.
8. Quick Quiz
Q1: Which API is used to detect when an element enters the browser viewport to trigger lazy loading?
A) MutationObserver
B) IntersectionObserver
Answer: B — IntersectionObserver monitors element intersections with the viewport, making it ideal for lazy loading image resources.
9. Scenario-Based Challenge
The Multi-Tab PDF Viewer Chunk Optimizer:
An application has a "PDF Export" tab. The PDF generation library: pdfmake.js (500KB) is only used on this tab. To prevent this library from slowing down the main page load, write code to lazy load the PDF library dynamically when the user clicks the "PDF Export" button.
10. Debugging Exercise
Explain why this dynamic import syntax fails to compile, and how to fix it:
// Objective: load module on user actions
// Bug: using string template variables in dynamic imports can break bundlers!
function loadWidget(name) {
import(`./widgets/${name}.js`).then(module => {
module.init();
});
}
View Solution
Diagnosis: Bundlers (like Webpack or Vite) perform static analysis to generate code chunks. If you pass a dynamic template string (like import(`./widgets/${name}.js`)) to a dynamic import, the bundler cannot determine which files to bundle, resulting in compilation failures or massive chunk sizes.
Fix: Avoid dynamic variables inside imports, or use static path mappings to guide the bundler:
// Option 1: Use explicit, static import paths const widgetImports = { chart: () => import('./widgets/chart.js'), table: () => import('./widgets/table.js') };
function loadWidget(name) { const loadFn = widgetImports[name]; if (loadFn) { loadFn().then(module => module.init()); } }
11. Interview Questions
🟢 Q1: Compare Static and Dynamic Imports and explain when to use each.
Answer:
• Static Imports (import module from 'path'): Evaluated statically during bundle compilation. The module is loaded immediately and bundled in the main chunk.
When to use: Essential modules required for the initial page render or core logic.
• Dynamic Imports (import('path')): Evaluated asynchronously at runtime. The module is split into a separate bundle chunk and loaded on demand.
When to use: Optional libraries (like charting engines), route components, or modals that are only loaded in response to user actions.
12. Production Considerations
- • Pre-fetching: For critical code chunks that are lazy-loaded (like the next page route in a flow), use link pre-fetching (
<link rel="preload">) to download resources in the background, minimizing delays when the user navigates.