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.

Hover to zoomIf 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.
| require() (CommonJS) | import (ES Modules) | |
|---|---|---|
| Syntax | const dep = require('dep') | import dep from 'dep' |
| Load Time | Runtime (Synchronous) | Compile-time (Asynchronous) |
| Tree Shaking | Not supported natively | Supported out-of-the-box |
| Scope | Can be called anywhere (inside ifs, functions) | Must be at the top level of the file |
| Default in Node.js | Yes (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.
// --- 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.
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.
// 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.
// 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:
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.
