Module
The Module pattern packages related code into a self-contained unit with a public interface and hidden internals, preventing namespace pollution. Native module systems now express it declaratively.
The Module pattern groups related functions, data, and state into a single self-contained unit that exposes a deliberate public interface while keeping the rest private. It solves the problems of namespace pollution and weak encapsulation, especially in languages without built-in access modifiers: without modules, helpers and state leak into the global scope where anything can read or clobber them.
How It Works
Classically in JavaScript, the pattern uses a closure — often an immediately invoked function expression — that declares private variables and functions inside its scope and returns an object exposing only the chosen public members. The returned object's methods retain access to the private scope through the closure, so internal state is reachable by the public API but invisible to outside code. A variant, the revealing module pattern, defines everything privately and returns an object that maps public names to selected internal functions.
Modern languages provide first-class module systems that achieve the same encapsulation declaratively: ES modules with export/import, Python modules and packages, Java packages and the module system, and Go packages with capitalization-based visibility. The principle is identical — a unit with a public surface and hidden internals.
When to Use It
Use the Module pattern to organize a codebase into cohesive units, to hide implementation details behind a stable interface, and to avoid global-scope collisions. It is foundational for any non-trivial application: each feature, service, or utility becomes a module with a clear contract. In legacy JavaScript it was the primary means of encapsulation before ES modules existed.
In languages with native module systems, prefer those over the closure-based idiom; reach for the manual closure technique mainly in constrained or legacy environments.
Trade-offs
Modules deliver encapsulation, clear public/private boundaries, reduced naming conflicts, and better testability and maintainability through cohesion and low coupling. They make large systems navigable and let internals change without breaking callers.
The closure-based form has drawbacks: private members are genuinely inaccessible, which can hinder testing of internals and complicate extension; each instance created via the closure duplicates function objects, costing memory if many are made; and singletons-by-module can hide global state. Over-modularization can also fragment code into too many tiny units, increasing cognitive overhead. Native module systems mitigate most of these concerns.
Related Patterns
The Module pattern overlaps with Facade, since a module's public interface often hides a complex internal subsystem. A module that exposes a single shared object behaves like a Singleton. Factory functions within a module commonly create the objects it manages. Namespaces and packages are language-level expressions of the same idea.
Example
A shopping-cart module exposes addItem, removeItem, and getTotal, while keeping the items array and a recalculate helper private. Outside code can only call the three public methods; it cannot mutate items directly or invoke the helper, so the cart's invariants stay intact. Migrating such code from CommonJS to ES modules preserves this boundary using native export statements instead of a returned object.