JavaScript design patterns help when they address a real source of change: complicated object creation, code that needs a clear boundary, or updates that must reach multiple consumers. They are reusable approaches, not a checklist or a guarantee of scalability. Start with the problem and the simplest mechanism that solves it.
What JavaScript design patterns solve
A design pattern is a reusable way to approach a recurring design problem. In a growing application, the useful question is not “Which patterns should I use?” but “What is changing, and how can I limit the cost of that change?” A pattern may reduce coupling or isolate changes, but it also adds concepts and indirection. Use it when that tradeoff fits the application.
As an Amazon Associate I earn from qualifying purchases.
JavaScript’s standardized core is ECMAScript. Browser APIs and other host capabilities surround the language; they are not themselves part of the ECMAScript specification. MDN’s overview of JavaScript technologies explains the distinction.
How to choose a pattern
Evaluate an approach against the pressure you actually have, rather than its name or popularity:
#1 Best Overall
- Coupling: How much must a consumer know about the implementation?
- Change isolation: If behavior changes, how many callers need edits?
- Complexity: Does the pattern remove complexity or merely move it behind another layer?
- Testing and instance isolation: Can tests create independent instances without shared state leaking between them?
- Lifecycle: Does the approach create subscriptions, resources, or other work that needs explicit cleanup?
- Existing mechanisms: Does JavaScript or your framework already provide the capability?
Module pattern: organize code behind a boundary
Use modules to group related behavior and control what other parts of an application can access. Modern JavaScript has native modules with import and export; they are the normal starting point for organizing application code. MDN’s JavaScript modules guide covers exports, imports, and dynamic loading.
// cart.js
export function total(items) {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// checkout.js
import { total } from "./cart.js";
export function checkout(items) {
return { amount: total(items) };
}
The module boundary lets callers depend on the exported API instead of the module’s internal details. Its cost is that boundaries and dependency direction still need thought; a file split alone does not make responsibilities coherent.
Rank #2
How this differs from the historical closure-based Module pattern
Older JavaScript examples often use a function closure to hide state and return a public object. That technique remains useful when a closure is specifically needed, but it should not be presented as the default alternative to native modules in modern application code. The underlying idea—encapsulate implementation behind a public interface—can be expressed with ECMAScript modules. MDN’s JavaScript Guide also covers closures and modules.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Factory pattern: separate requests from construction
Consider a factory when callers should ask for a product without owning every construction detail, or when the product varies by input or environment. Patterns.dev describes Factory as an object-creation pattern.
function createNotifier(channel) {
if (channel === "email") {
return { send: message => sendEmail(message) };
}
if (channel === "console") {
return { send: message => console.log(message) };
}
throw new Error(`Unsupported channel: ${channel}`);
}
const notifier = createNotifier("console");
notifier.send("Build complete");
The caller depends on the returned capability, not the details of choosing and assembling an implementation. This can make a change to construction local. The tradeoff is another layer to understand; if there is only one simple object and no variation to hide, direct construction is clearer.
Observer pattern: notify multiple interested consumers
Use an observer-style subscription when one event or state change needs to notify multiple consumers that should not be tightly coupled to the producer. Patterns.dev describes observables as notifying subscribers when an event occurs.
Rank #4
function createEmitter() {
const listeners = new Set();
return {
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
publish(value) {
for (const listener of [...listeners]) {
try {
listener(value);
} catch (error) {
queueMicrotask(() => { throw error; });
}
}
}
};
}
const changes = createEmitter();
const unsubscribe = changes.subscribe(value => render(value));
changes.publish({ status: "saved" });
unsubscribe();
This example makes lifecycle and behavior explicit: subscribing returns a cleanup function; publishing iterates over a snapshot so changes to subscriptions during notification do not alter the current iteration; and one listener’s synchronous error does not prevent later listeners from running. Rethrowing asynchronously surfaces the error, but applications may instead choose a central error handler. Observer systems require deliberate choices about cleanup, error handling, and ordering; for a single consumer, a direct function call is often simpler.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Singleton: share an instance only when sharing is intentional
A singleton provides shared access to one instance. It can suit a resource that genuinely must be shared, but global shared state makes dependencies less visible and can make tests or isolated application instances harder to reason about. Patterns.dev includes Singleton among its JavaScript pattern topics.
Best Value
// Prefer explicit ownership when possible.
function createApplication({ storage }) {
return {
save(key, value) {
storage.setItem(key, value);
}
};
}
const app = createApplication({ storage: window.localStorage });
Passing the dependency makes ownership and test substitution explicit. A singleton may still be justified for a truly process-wide or application-wide resource; avoid making unrelated state global merely to ensure there is one instance.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Proxy, Mediator, and middleware: add interception or coordination for a reason
Patterns.dev’s catalog also includes Proxy, Mediator, and other patterns. They are options for concrete pressures, not steps every application should take.
- Proxy: Consider it when access to an object needs controlled interception, such as validation or lazy behavior. Interception can make behavior less visible at the call site.
- Mediator: Consider it when components are becoming tangled through direct coordination and a central interaction point would reduce those dependencies. The mediator itself can become a new concentration of complexity.
- Middleware: Consider it when request or message processing needs composable stages. The order of stages and how errors or short-circuits behave must be clear.
If the application has no meaningful interception, coordination, or processing pipeline problem, these abstractions are unnecessary.
Free tools Windows power users keep installed
One-click scans. No signup required.
Where to learn more
Patterns.dev is a free resource covering design, rendering, and performance patterns for vanilla JavaScript and modern frameworks. Its catalog includes Module, Factory, Observer, Singleton, Proxy, and related topics.
Addy Osmani’s Learning JavaScript Design Patterns is a book-length resource focused on this subject. Check which edition you are using and compare language-specific examples with current documentation, especially for modern modules. MDN’s JavaScript Guide is a current reference for language features including modules, classes, promises, and closures.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




