Skip to content
Squarebrackets Private Limited
Squarebrackets
Courses
Browse
Explore
Services
Learning
Prep
Code&Run
Whiteboard
LMS

Web Development

Import vs Require in JavaScript: Key Differences Explained

Understand the differences between import and require in JavaScript. Learn when to use ES Modules (ESM) versus CommonJS (CJS) with real-world examples.

Gs Dwivedi
Gs Dwivedi
· 9 min read · Updated
Import vs Require in JavaScript: Key Differences ExplainedHover to zoom

If you have spent any time writing Node.js backend services or building React frontends, you have likely run into a frustrating syntax error. Understanding the nuances of import vs require in javascript is essential when a tutorial tells you to use `const express = require('express')` while another insists on `import express from 'express'`. This clash highlights the fundamental battle between two module systems: CommonJS (CJS) and ES Modules (ESM). Mastering how these loaders behave prevents configuration headaches and ensures your apps scale smoothly.

At Squarebrackets, we see developers struggle with this distinction daily. They copy a code snippet from StackOverflow, paste it into a modern Vite project, and watch their terminal explode with errors. Let's break down exactly why these two systems exist, how they behave under the hood, and which one you should use for your next production deployment.

What is the Difference Between Import and Require in JavaScript?

The core difference lies in when and how your code is loaded and parsed. `require()` is a built-in synchronous function used in CommonJS to load modules at runtime. On the other hand, the `import` statement is part of the native ECMAScript Modules standard, which is analyzed statically at compile time before your code actually executes.

Because `require()` is synchronous, it pauses execution until the requested module is fully loaded and executed. This was perfect for local server environments in early Node.js but causes issues when blocking the main thread on modern, highly responsive web browsers.

Execution flow comparison between CommonJS runtime loading and ES Modules compile-time analysis. A flowchart showing how CommonJS loads modules synchronously at runtime while ES Modules use static analysis for optimization.
require() (CommonJS)import (ES Modules)
Syntaxconst dep = require('dep')import dep from 'dep'
Load TimeRuntime (Synchronous)Compile-time (Asynchronous)
Tree ShakingNot supported nativelySupported out-of-the-box
ScopeCan be called anywhere (inside ifs, functions)Must be at the top level of the file
Default in Node.jsYes (legacy and standard)Yes, if configured via package.json

Syntax Comparison: How They Look in Code

Let us look at how you export and import values in both systems. Note how CommonJS uses a global `module.exports` object, while ES Modules use explicit `export` and `export default` keywords.

javascript
// --- CommonJS Style (require) ---
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = { add, subtract };

// app.js
const { add, subtract } = require('./math');
console.log(add(5, 10));

// --- ES Modules Style (import) ---
// helper.js
export const multiply = (a, b) => a * b;
export default function greet(name) {
  return `Hello, ${name}`;
}

// main.js
import greet, { multiply } from './helper.js';
console.log(multiply(4, 5));

Why Compile-Time Parsing Matters: Static vs Dynamic

The fact that `import` is static is its biggest superpower. Because the JavaScript engine reads `import` statements before running any code, bundlers like Webpack, Rollup, or Vite can map out a dependency graph of your entire application.

This enables a feature called tree-shaking. If you import a utility library containing 500 helper functions but only use two, the bundler can safely discard the other 498 functions from your final production bundle. With `require()`, the engine cannot determine what you need until the code actually runs, meaning you must ship the entire library to the user.

Pro-Tip for Modern Node.js Developers
If you are starting a new backend project in Node.js v18+, save yourself future migration headaches. Open your package.json and add `"type": "module"`. This lets you use modern import/export syntax natively without needing Babel or TypeScript compilation.

Can You Load Modules Conditionally?

Because `require()` is a standard function, you can place it inside `if` statements, loops, or function bodies. This is highly useful for loading platform-specific code dynamically or saving memory during startup.

javascript
// This works perfectly in CommonJS
if (process.env.NODE_ENV === 'development') {
  const logger = require('./devLogger');
  logger.log('Dev mode active');
}

Standard `import` statements cannot be nested inside conditions or functions; they must remain at the very top of your file. However, the ES Modules specification provides a special function-like syntax called dynamic import() to solve this exact problem.

javascript
// Dynamic import in ES Modules (returns a Promise)
if (isUserLoggedIn) {
  import('./dashboard.js')
    .then((module) => {
      module.renderDashboard();
    })
    .catch((err) => {
      console.error('Failed to load dashboard module', err);
    });
}

Understanding __dirname and __filename

In CommonJS, you get access to global injection variables like `__dirname` (the directory name of the current module) and `__filename`. If you try to use these inside an ES Module, Node.js will throw a `ReferenceError` because those identifiers are scoped specifically to CommonJS wrapper functions.

To achieve the same result in an ES Module environment, you must reconstruct them using the `import.meta.url` object and the native `url` module:

javascript
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

console.log(__dirname);

Production Reality and Common Migration Pitfalls

When migrating a large legacy codebase from CommonJS to ES Modules, developers frequently trip over file extensions. In native ESM, relative import paths must include the file extension, such as `import { validate } from './utils.js'` instead of `import { validate } from './utils'`. Omitting the extension results in module resolution failures in Node.js.

Another common issue involves circular dependencies. CommonJS handles circular dependencies by returning a partial exports object, which can sometimes lead to undefined properties if functions are invoked during loading. ES Modules handle circular dependencies through live bindings, pointing directly to the exported variable memory address once initialization completes.

Conclusion: Which One Should You Choose?

Understanding the core differences of module loading ensures you can confidently configure your projects, debug module errors, and build fast, modern web applications. Before writing your routes, you might want to review Creating Server with Express and HTTP Methods: A Practical Guide to set up your handlers correctly. If you want to master these concepts deeply with hands-on labs and microservices architectures, join our industry-aligned MERN Stack with Microservices & Gen AI training program.


import vs require javascriptcommonjs vs es modulesnode js requirejavascript import statementcjs vs esmrequire vs import nodejavascript module systemsonline web development

Frequently asked questions