Skip to content

Dynamic Import

This lesson explains Dynamic Import with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.

ES6 Dynamic Imports Overview

Dynamic imports allow JavaScript modules to be loaded only when they are needed. Instead of loading every module during the initial page load, dynamic imports use the import() function to load code asynchronously at runtime.

Dynamic imports are commonly used for code splitting, lazy loading, route-based loading, modal loading, chart libraries, admin features, language files, heavy utilities, feature flags, and performance optimization in modern JavaScript applications.

Feature Description
Syntax import("./module.js")
Return Value Returns a Promise.
Loading Time Runtime, when code is needed.
Main Benefit Improves initial load performance through lazy loading.
Works With Default exports, named exports, async await, bundlers, routes.
Common Tools Vite, Webpack, Rollup, Parcel, Next.js, Astro.

Static Import vs Dynamic Import

// Static import
// Loaded before the module runs

import {
  formatPrice
}
from "./formatters.js";

console.log(
  formatPrice(100)
);
// Dynamic import
// Loaded only when this code runs

import("./formatters.js")
  .then((module) => {
    console.log(
      module.formatPrice(100)
    );
  });

Static imports are loaded upfront. Dynamic imports are loaded later when the application actually needs them.

Basic Dynamic Import Example

async function loadFormatter() {

  const module =
    await import("./formatters.js");

  const result =
    module.formatPrice(99.99);

  console.log(result);

}

loadFormatter();

Because import() returns a Promise, it works naturally with async and await.

Dynamic Import with Named Exports

// math.js

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}
// app.js

async function calculate() {

  const math =
    await import("./math.js");

  console.log(
    math.add(10, 20)
  );

  console.log(
    math.multiply(5, 4)
  );

}

calculate();

Named exports are accessed as properties on the imported module object.

Dynamic Import with Default Export

// User.js

export default class User {

  constructor(name) {
    this.name =
      name;
  }

}
// app.js

async function loadUser() {

  const module =
    await import("./User.js");

  const User =
    module.default;

  const user =
    new User("Alice");

  console.log(user);

}

loadUser();

Default exports are available through module.default.

Destructuring Dynamic Imports

async function loadHelpers() {

  const
  {
    formatDate,
    formatCurrency
  }
  =
  await import("./helpers.js");

  console.log(
    formatDate(new Date())
  );

  console.log(
    formatCurrency(1200)
  );

}

Destructuring helps directly extract named exports from the loaded module.

Load Module on Button Click

document
  .querySelector("#loadChart")
  .addEventListener("click", async () => {

    const chartModule =
      await import("./chart.js");

    chartModule.renderChart(
      "#chartContainer"
    );

  });

This pattern loads chart code only when the user clicks the button.

Route-Based Dynamic Imports

const routes =
  {
    "/":
      () => import("./pages/HomePage.js"),

    "/products":
      () => import("./pages/ProductsPage.js"),

    "/admin":
      () => import("./pages/AdminPage.js")
  };

async function loadRoute(path) {

  const loadPage =
    routes[path];

  if (!loadPage) {
    return;
  }

  const pageModule =
    await loadPage();

  pageModule.render();

}

Route-based dynamic imports are common in single-page applications to split each page into a separate JavaScript chunk.

Conditional Dynamic Import

async function loadAdminTools(user) {

  if (user.role !== "admin") {
    return;
  }

  const adminTools =
    await import("./admin-tools.js");

  adminTools.initialize();

}

Conditional imports help load features only for users who need them.

Feature-Based Dynamic Import

async function enableFeature() {

  if ("IntersectionObserver" in window) {
    const lazy =
      await import("./lazy-load.js");

    lazy.enableLazyLoading();
  } else {
    const fallback =
      await import("./lazy-load-fallback.js");

    fallback.enableFallback();
  }

}

Dynamic imports work well with feature detection and fallback modules.

Load Language Files Dynamically

async function loadLanguage(language) {

  const messages =
    await import(
      "./languages/" + language + ".js"
    );

  return messages.default;

}

loadLanguage("en")
  .then((messages) => {
    console.log(messages);
  });

Internationalized applications often load only the current user's language file.

Load Heavy Library Only When Needed

async function exportPdf() {

  const pdf =
    await import("./pdf-export.js");

  pdf.generatePdf();

}

document
  .querySelector("#exportPdf")
  .addEventListener(
    "click",
    exportPdf
  );

Heavy features such as PDF export, charts, maps, editors, and analytics tools should often be loaded on demand.

Error Handling with Dynamic Imports

async function loadFeature() {

  try {

    const feature =
      await import("./feature.js");

    feature.start();

  } catch (error) {

    console.error(
      "Failed to load feature:",
      error.message
    );

  }

}

Dynamic imports can fail if the file path is wrong, the network is down, or the generated chunk cannot be loaded.

Show Loading State

async function openReport() {

  const button =
    document.querySelector("#reportButton");

  button.disabled =
    true;

  button.textContent =
    "Loading...";

  try {

    const report =
      await import("./report.js");

    report.render();

  } finally {

    button.disabled =
      false;

    button.textContent =
      "Open Report";

  }

}

Show loading feedback when dynamically loaded modules may take time.

Preload Module on Hover

let reportModulePromise =
  null;

function preloadReport() {

  if (!reportModulePromise) {
    reportModulePromise =
      import("./report.js");
  }

}

async function openReport() {

  const report =
    await reportModulePromise;

  report.render();

}

document
  .querySelector("#reportButton")
  .addEventListener(
    "mouseenter",
    preloadReport
  );

document
  .querySelector("#reportButton")
  .addEventListener(
    "click",
    openReport
  );

Preloading on hover improves perceived performance by starting the module download before the click happens.

Cache Dynamic Import Result

let chartModule =
  null;

async function getChartModule() {

  if (!chartModule) {
    chartModule =
      await import("./chart.js");
  }

  return chartModule;

}

async function renderChart() {

  const chart =
    await getChartModule();

  chart.renderChart();

}

Browsers and bundlers usually cache modules, but storing the loaded module reference can make application logic easier to control.

Webpack Chunk Name Example

async function loadAdmin() {

  const admin =
    await import(
      /* webpackChunkName: "admin" */
      "./admin.js"
    );

  admin.start();

}

Some bundlers support special comments to control chunk names or preloading behavior.

Vite import.meta.glob Example

const pages =
  import.meta.glob(
    "./pages/*.js"
  );

async function loadPage(name) {

  const pagePath =
    "./pages/" + name + ".js";

  const load =
    pages[pagePath];

  if (!load) {
    return;
  }

  const page =
    await load();

  page.render();

}

import.meta.glob is commonly used in Vite projects for dynamic route or file-based module loading.

Dynamic Import vs Static Import

Feature Static Import Dynamic Import
Syntax import x from "./x.js" import("./x.js")
Loading Before module execution. At runtime when called.
Returns Imported binding. Promise resolving to module object.
Best Use Core application code. Lazy loaded or conditional code.
Can Be Conditional? No. Yes.
Code Splitting Usually bundled upfront. Creates separate chunks in bundlers.

Common Dynamic Import Use Cases

  • Lazy loading pages.
  • Route-based code splitting.
  • Loading admin-only features.
  • Loading modals on demand.
  • Loading chart libraries.
  • Loading map libraries.
  • Loading language files.
  • Loading PDF or export tools.
  • Feature flag based loading.
  • Browser feature fallback loading.

Common Dynamic Import Mistakes

  • Forgetting that import() returns a Promise.
  • Trying to use dynamically imported modules synchronously.
  • Forgetting module.default for default exports.
  • Using fully dynamic paths that bundlers cannot analyze.
  • Not handling loading errors.
  • Not showing loading states for slow chunks.
  • Lazy loading tiny modules unnecessarily.
  • Creating too many small chunks.
  • Loading critical code too late.
  • Ignoring browser or network failures.

Dynamic Import Best Practices

  • Use dynamic imports for heavy or optional features.
  • Keep core application code statically imported.
  • Handle errors with try...catch.
  • Show loading states when imports are user-triggered.
  • Cache loaded modules when reused frequently.
  • Use route-level code splitting for large applications.
  • Preload important modules on hover or idle time.
  • Avoid over-splitting into too many tiny chunks.
  • Use bundler-supported patterns for dynamic file paths.
  • Test production builds because bundler behavior matters.

Dynamic Import Interview Questions

  • What is a dynamic import?
  • How is import() different from static import?
  • What does import() return?
  • How do you access a default export from a dynamic import?
  • How do you access named exports from a dynamic import?
  • How does dynamic import help performance?
  • What is code splitting?
  • When should dynamic imports be avoided?
  • How do bundlers handle dynamic imports?
  • What are common dynamic import mistakes?

Key Takeaways

  • import() loads JavaScript modules at runtime.
  • Dynamic imports return a Promise.
  • Use await import("./module.js") inside async functions.
  • Default exports are accessed with module.default.
  • Named exports are accessed from the module object or destructuring.
  • Dynamic imports improve performance by loading code only when needed.
  • Bundlers use dynamic imports for code splitting.
  • Always handle loading errors and slow network cases.

Pro Tip

Use dynamic imports for code that users may not need immediately, such as admin tools, reports, charts, modals, maps, and export features. Keep critical above-the-fold logic in static imports so the application starts quickly and predictably.