← Back to Articles         
What is Code Splitting in Angular?

What is Code Splitting in Angular?

Published on 02 Oct 2026     8 min read Angular
Lazy Loading

What is Code Splitting in Angular?

1. What is Code Splitting?

Code splitting is a technique that divides an Angular application's JavaScript code into smaller bundles/chunks instead of delivering all application code as one large initial bundle.

The browser downloads the initial code first and can download other chunks when they are needed.

Code Splitting = Break a large JavaScript bundle into smaller chunks.

For example, suppose an application contains:

Home
Products
Orders
Admin
Reports

Instead of producing one conceptual large bundle:

main.js
 ├── Home
 ├── Products
 ├── Orders
 ├── Admin
 └── Reports

code splitting can produce separate chunks:

main.js
products-[hash].js
orders-[hash].js
admin-[hash].js
reports-[hash].js

The exact generated filenames and chunk boundaries are determined by Angular's build tooling.


2. Code Splitting and Lazy Loading

These two concepts are closely related, but they are not exactly the same thing.

Code Splitting
      ↓
Break code into separate chunks

Lazy Loading
      ↓
Load a chunk only when it is needed

A useful way to remember:

Code splitting creates the pieces; lazy loading determines when deferred pieces are loaded.

For example:

{
  path: 'products',
  loadComponent: () =>
    import('./products/products')
      .then(m => m.Products)
}

The dynamic:

import('./products/products')

creates a lazy boundary that allows the build system to place that code into a separate chunk.

Then loadComponent() tells Angular Router when to load it.


3. Full Simple Sample Program

Suppose we have three pages:

Home
Products
Admin

We want:

  • Home → eagerly loaded
  • Products → lazy-loaded
  • Admin → lazy-loaded

Project Structure

src/app/
│
├── app.ts
├── app.html
├── app.routes.ts
│
├── home/
│   └── home.ts
│
├── products/
│   └── products.ts
│
└── admin/
    └── admin.ts

app.routes.ts

import { Routes } from '@angular/router';
import { Home } from './home/home';

export const routes: Routes = [
  {
    path: '',
    component: Home
  },
  {
    path: 'products',
    loadComponent: () =>
      import('./products/products')
        .then(m => m.Products)
  },
  {
    path: 'admin',
    loadComponent: () =>
      import('./admin/admin')
        .then(m => m.Admin)
  }
];

app.ts

import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    RouterLink,
    RouterOutlet
  ],
  templateUrl: './app.html'
})
export class App {
}

app.html

<h1>My Application</h1>

<nav>
  <a routerLink="/">Home</a> |
  <a routerLink="/products">Products</a> |
  <a routerLink="/admin">Admin</a>
</nav>

<hr>

<router-outlet></router-outlet>

home/home.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  standalone: true,
  template: `
    <h2>Home</h2>
    <p>Welcome to Home.</p>
  `
})
export class Home {
}

products/products.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-products',
  standalone: true,
  template: `
    <h2>Products</h2>

    <ul>
      <li>Laptop</li>
      <li>Keyboard</li>
      <li>Mouse</li>
    </ul>
  `
})
export class Products {
}

admin/admin.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-admin',
  standalone: true,
  template: `
    <h2>Admin</h2>
    <p>Welcome to Admin.</p>
  `
})
export class Admin {
}

4. How Does Code Splitting Happen?

Look at the Products route:

{
  path: 'products',
  loadComponent: () =>
    import('./products/products')
      .then(m => m.Products)
}

The important part is:

import('./products/products')

This is a dynamic import.

Compare it with a normal static import.

Static import

import { Products } from './products/products';

Then:

{
  path: 'products',
  component: Products
}

The Products code is part of the eagerly reachable dependency graph.


Dynamic import

import('./products/products')

This establishes a separate asynchronous loading boundary.

Conceptually, the build output might look like:

dist/
│
├── main-[hash].js
├── products-[hash].js
└── admin-[hash].js

Again, the actual filenames and chunk structure can vary.


5. Application Flow

When the application starts:

Browser
   ↓
Download initial application bundles
   ↓
Home displayed

The Products and Admin route code can remain deferred.

When the user clicks:

Products

Angular does:

/products
    ↓
Angular Router
    ↓
loadComponent()
    ↓
dynamic import()
    ↓
Products chunk requested
    ↓
Products component created
    ↓
Products displayed

Similarly:

/admin
    ↓
Admin chunk loaded
    ↓
Admin displayed

6. Without Code Splitting vs With Code Splitting

Without route-level code splitting

Conceptually:

Application
     ↓
Large initial bundle

┌──────────────────────┐
│ Home                 │
│ Products             │
│ Orders               │
│ Reports              │
│ Admin                │
└──────────────────────┘

The user may never visit Admin, but Admin code may still be included in the eagerly loaded dependency graph.


With Code Splitting

Initial
   ↓
main.js
   ↓
Home

User → Products
   ↓
products chunk

User → Admin
   ↓
admin chunk

Now the initial workload can be smaller.


7. loadComponent() and Code Splitting

For a single standalone component:

{
  path: 'products',
  loadComponent: () =>
    import('./products/products')
      .then(m => m.Products)
}

Think:

loadComponent()
      ↓
Dynamic import
      ↓
Separate lazy chunk
      ↓
Downloaded when route is needed

8. loadChildren() and Code Splitting

Code splitting can also happen at the feature route level.

Suppose Admin has:

Admin
 ├── Dashboard
 ├── Users
 └── Reports

Main route:

{
  path: 'admin',
  loadChildren: () =>
    import('./admin/admin.routes')
      .then(m => m.ADMIN_ROUTES)
}

Then:

Main Application
      |
      +--- main chunk
      |
      +--- Admin lazy boundary
              |
              +--- Dashboard
              +--- Users
              +--- Reports

And the individual Admin pages can themselves use loadComponent():

export const ADMIN_ROUTES: Routes = [
  {
    path: 'dashboard',
    loadComponent: () =>
      import('./dashboard')
        .then(m => m.Dashboard)
  },
  {
    path: 'users',
    loadComponent: () =>
      import('./users')
        .then(m => m.Users)
  }
];

This allows additional splitting inside the feature.


9. Code Splitting vs Lazy Loading

This is an important interview distinction.

Code Splitting Lazy Loading
Divides code into chunks Loads deferred code when required
Build/bundling concept Runtime loading strategy
Creates separate bundles/chunks Controls when those chunks are requested
Dynamic import() enables a split point Router APIs use dynamic imports for lazy routes
Improves modular delivery Reduces initial loading work

Easy way to remember

Imagine you have a 500-page book.

Code splitting:

500-page book
      ↓
Split into
      ↓
Book 1
Book 2
Book 3
Book 4
Book 5

Lazy loading:

Need Book 1
   ↓
Get Book 1

Later need Book 4
   ↓
Get Book 4

So:

Splitting = divide. Lazy loading = load on demand.


10. Advantages of Code Splitting

Smaller initial JavaScript workload

Instead of downloading feature code immediately, the initial bundle can contain only what is needed for startup.

Faster initial loading

Less JavaScript may need to be:

Downloaded
    ↓
Parsed
    ↓
Compiled
    ↓
Executed

Better for large applications

Especially useful for applications with separate areas such as:

Public
Admin
Reports
Analytics
Settings
Management

Users don't immediately download unused features

For example, a normal user may never access:

/admin

The Admin feature can remain deferred.


11. Disadvantages

Code splitting also has some trade-offs.

Additional network requests

When the user first visits a lazy feature, the browser may need to request another JavaScript chunk.

Click Admin
    ↓
Download Admin chunk
    ↓
Display Admin

This can introduce a small delay on first navigation.

Too many tiny chunks

Excessive splitting can create unnecessary loading overhead.

Therefore, features should be split at sensible boundaries.


12. Code Splitting in Angular + ASP.NET Core

Code splitting concerns your Angular frontend code.

It is different from loading application data from ASP.NET Core.

For example:

Browser
   ↓
Angular initial bundle
   ↓
Home

User clicks Products
   ↓
Products Angular chunk loaded
   ↓
Products component starts
   ↓
HttpClient
   ↓
ASP.NET Core Web API
   ↓
SQL Server
   ↓
JSON data returned

There are two separate operations:

Angular Code
   ↓
Code Splitting + Lazy Loading

Product Data
   ↓
HTTP API Request

Key Points

  1. Code splitting divides application JavaScript into smaller chunks.

  2. It helps reduce the amount of JavaScript required during initial startup.

  3. Dynamic import() creates an asynchronous boundary that bundlers can split.

import('./products/products')
  1. Angular routing commonly uses code splitting through:
loadComponent()

and:

loadChildren()
  1. loadComponent() can split and lazy-load an individual standalone component.

  2. loadChildren() can split a feature's routing configuration.

  3. Code splitting and lazy loading are related but different:

Code Splitting
      ↓
Creates separate chunks

Lazy Loading
      ↓
Loads deferred chunks when needed

Interview Questions and Answers

1. What is code splitting in Angular?

Code splitting is the process of dividing an Angular application's JavaScript into smaller chunks so that all feature code doesn't need to be included in the initial load.


2. Why do we need code splitting?

It can reduce the initial JavaScript bundle and improve startup performance by deferring code for features that aren't immediately required.


3. How is code splitting implemented in Angular?

A common approach is using dynamic import() through Angular Router APIs such as:

loadComponent()

and:

loadChildren()

4. What is the difference between code splitting and lazy loading?

Code splitting divides the application into separate chunks.

Lazy loading loads deferred chunks only when they're required.

Code Splitting → Divide
Lazy Loading   → Load on demand

5. What role does import() play?

Dynamic import() creates an asynchronous module-loading boundary that allows the build system to generate separate chunks.

import('./products/products')

6. Does loadComponent() support code splitting?

Yes. For example:

loadComponent: () =>
  import('./products/products')
    .then(m => m.Products)

The Products component can be placed behind a separate lazy-loading boundary and loaded when the route is accessed.


7. Does loadChildren() support code splitting?

Yes. It can lazy-load an entire feature route configuration:

loadChildren: () =>
  import('./admin/admin.routes')
    .then(m => m.ADMIN_ROUTES)

Interview Quick Revision

              CODE SPLITTING
                    │
                    │
          Break application code
          into smaller chunks
                    │
          ┌─────────┴─────────┐
          │                   │
   loadComponent()      loadChildren()
          │                   │
   Single component      Feature routes
          │                   │
          └─────────┬─────────┘
                    │
              Lazy Loading
                    │
           Load when required

One-line interview answer:

Code splitting divides an Angular application into smaller JavaScript chunks, while lazy loading allows those deferred chunks to be loaded only when they are needed.

← Back to Articles