Back
Web Fundamentals

Code Splitting: Optimize Bundle Size with Dynamic Imports

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
mediumPerformance

Code Splitting: Optimize Bundle Size with Dynamic Imports

TL;DRSplit code by routes and heavy features using dynamic imports. Load only what the user needs, when they need it.
High Signal
Google
Meta
Netflix
Agoda
Meesho
30-Second Answerstart every interview with this

Code splitting breaks your JavaScript bundle into smaller chunks that load on demand. It dramatically reduces initial load time by shipping only the code required for the current view. Use dynamic imports (`import()`) with React.lazy() and Suspense for seamless integration.

Instead of packing everything for every trip (large initial bundle), pack only what you need for today's journey (route) and open side pockets (heavy components) only when required. Dynamic imports let you unzip those pockets on demand.

Initial Load (Core + Current Route)
Navigation → New Chunk Loads
User Action → Heavy Component Loads
Smaller Bundles = Faster Startup

1Route-Based Splitting

Automatically split by page/route. Each route becomes its own chunk, loaded only when the user navigates there.

App.tsxtsx
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

<Suspense fallback={<Loading />}>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/dashboard" element={<Dashboard />} />
  </Routes>
</Suspense>

2Component-Based & Dynamic Imports

Split heavy components (charts, modals, editors) and load them conditionally on interaction or visibility.

3Vendor Splitting

Separate third-party libraries into their own chunk for better long-term caching since they change less frequently.

PropertyRoute-BasedComponent-Based
ConsNavigation delay possibleRequires loading states, More manual
ProsNatural boundaries, Easy with React Router / Next.js, Good for large appsFine-grained control, Load only when needed
Best ForMulti-page applicationsHeavy modals, charts, editors

Route-Based

Cons

Navigation delay possible

Pros

Natural boundaries, Easy with React Router / Next.js, Good for large apps

Best For

Multi-page applications

Component-Based

Cons

Requires loading states, More manual

Pros

Fine-grained control, Load only when needed

Best For

Heavy modals, charts, editors

Common questions

  • ›“How do you implement code splitting in a React app?”
  • ›“What are the trade-offs of lazy loading?”
  • ›“How does dynamic import() work under the hood?”
  • ›“When should you split code vs keep it together?”

What interviewers look for

  • Practical use of React.lazy() + Suspense
  • Understanding of bundle impact and user experience
  • Awareness of loading states and error boundaries
  • Data-driven approach (measure before/after)

Short answer (60 sec)

Use dynamic imports (`import()`) and React.lazy() to split code by routes and heavy components. Wrap lazy components in Suspense with meaningful fallbacks. This reduces initial bundle size and improves LCP.

Detailed answer (senior level)

Route-based splitting gives natural chunking for navigation. Component-based splitting targets heavy features like charts or modals. Vendor splitting improves cache longevity. Combine with prefetching on hover and proper loading/error states. Always measure bundle sizes and real-user metrics — splitting too aggressively can hurt navigation experience.

  • Lazy loading critical above-the-fold components
  • Forgetting Suspense fallbacks (blank screens)
  • Not handling errors in dynamic imports
  • Over-splitting into tiny chunks (too many requests)
  • Ignoring prefetching for likely next routes
Key Takeaways
  • ✓Code splitting reduces initial bundle size and improves LCP
  • ✓Use React.lazy() + Suspense for easy route/component splitting
  • ✓Dynamic imports enable conditional and on-demand loading
  • ✓Separate vendor code for better caching
  • ✓Always provide loading states and error boundaries
  • ✓Measure bundle analyzer output and real-user performance
  • ✓Balance splitting with user-perceived latency
Previous TopicCritical Resource Prioritization: Optimize Loading OrderNext Topic Tree Shaking: Eliminate Dead Code from Your Bundle

On this page