Modules
Splitting code across multiple files, and sharing pieces between them deliberately.
What is it?
A real application quickly grows beyond a single file. Modules let you split code into separate files, each responsible for one thing, and explicitly control what's shared between them using export (to make something available to other files) and import (to bring it in).
Explain like I'm 10
Think of modules like separate departments in a company. Each department (file) does its own work internally, but only shares specific documents (exports) with other departments that specifically ask for them (imports) — nobody has to see everything happening everywhere.
Examples
Exporting and importing
// math.js
export function add(a, b) {
return a + b;
}
export const PI = 3.14159;
// app.js
import { add, PI } from "./math.js";
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159A default export
// user.js
export default class User {
constructor(name) {
this.name = name;
}
}
// app.js
import User from "./user.js"; // any name works for a default import
const amara = new User("Amara");A module can have at most one default export, and the importer is free to name it whatever they like — unlike named exports, which must be imported by their exact name.
How it works
Each file is its own module with its own private scope — nothing inside it is visible elsewhere unless explicitly exported. When a file imports from another, JavaScript loads that module (once, even if imported from many places), runs it, and hands over exactly the exported bindings that were requested.
Why does it exist?
Without modules, every file's code shares one giant global scope — names collide, and there's no way to tell what depends on what just by looking at a file. Modules give every file its own scope and make dependencies explicit and traceable.
When to use it
Split code into modules as soon as a single file starts covering more than one clear responsibility — a set of utility functions, a component, a set of related constants. Import only the specific pieces a file actually needs.
When not to use it
For a truly tiny script, splitting into multiple files and modules can add more overhead (import paths to manage) than it saves. And avoid modules that import from each other in a circular way (A imports B, which imports A) — it's a common source of confusing bugs.
Common mistakes
Forgetting the file extension or path in an import in environments that require it.
Exporting far more than a module actually needs to share, making its real public surface unclear.
Creating circular imports between two modules that depend on each other.
Practice exercises
- Easy:
Create a module that exports a
greet(name)function, and import it into another file to use it. - Medium:
Create a module with both a default export and a couple of named exports, and import all of them correctly.
- Hard:
Split a small script's logic into three modules with clear, single responsibilities, importing only what each one needs.
Interview questions
What's the difference between a named export and a default export?
A module can have many named exports (imported with exact names in {}) but only one default export (imported under any name you choose).
Why are modules better than one giant global script?
They keep variables scoped to their own file by default, and make what's shared between files explicit and traceable through imports.
What is a circular dependency?
When two modules import from each other, directly or indirectly, which can cause one of them to receive an incomplete, partially-loaded version of the other.