Concept
The beginner framing: a design pattern is a reusable solution to a commonly recurring problem in software design, not a library you import, but a shape of code you can recognize and apply.
The precise mental model: most classic design patterns (from the 1994 "Gang of Four" book) were codified for languages like C++ and Java, where you don't have first-class functions or closures, so achieving things like "private state" or "a function you can pass around" required entire class hierarchies. JavaScript's closures (Closures) and prototypes (Prototypes) make several of these patterns dramatically simpler, and this topic focuses specifically on how they show up in idiomatic JavaScript, not the textbook UML-diagram version.
Module Pattern, the pattern you already know
const CounterModule = (function () {
let count = 0; // PRIVATE, unreachable from outside this IIFE's scope
return {
increment() { return ++count; },
reset() { count = 0; },
};
})();
CounterModule.increment(); // 1
CounterModule.count; // undefined, truly private, not just "by convention"This is exactly the closure-based privacy pattern from Closures, applied deliberately as a design pattern, and it's the historical reason ES Modules (every file's top-level variables are private by default) feel so natural to JS developers: they formalized a pattern the community had already been hand-rolling for years.
Singleton, usually a module, not a class, in JS
// config.js, this IS the singleton, by virtue of ESM module caching
let settings = { theme: "dark" };
export function getSettings() { return settings; }
export function updateSettings(patch) { settings = { ...settings, ...patch }; }Every import of this module gets the exact same, single instance, ESM's module cache (see Modules) guarantees a file's code runs once, and every importer shares the same live bindings. The textbook Singleton pattern (a class with a private constructor and a static getInstance() method guarding a single instance) solves a problem JavaScript modules already solve for free, writing a class-based Singleton in JS is usually a sign of importing a pattern from another language rather than reaching for the idiomatic tool.
Factory, a function that returns different shapes based on input
function createShape(type, size) {
switch (type) {
case "circle": return { type, area: () => Math.PI * size ** 2 };
case "square": return { type, area: () => size ** 2 };
default: throw new Error(`Unknown shape: ${type}`);
}
}
const shapes = ["circle", "square"].map((type) => createShape(type, A factory centralizes object creation logic, useful when construction involves real decision-making (which variant to build, what defaults to apply) rather than being a trivial one-liner, where it would just be needless indirection.
Observer / Pub-Sub, the pattern behind every event system you've used
function createEventBus() {
const listeners = new Map(); // event name -> Set of callbacks
return {
on(event, callback) {
if (!listeners.has(event)) listeners.set(event, new Set());
listeners.get(event).add(callback);
},
off(event, callback) {
listeners.get(event)?.delete(callback);
},
emit(event, payload) {
listeners.get(event)?.forEach((
This is the exact mechanism behind EventTarget/addEventListener in the DOM, Node's EventEmitter, and most state-management libraries at their core, subscribers register interest in named events, and a publisher fires them without needing to know who's listening.
Decorator, wrapping a function to add behavior, without modifying it
function withLogging(fn) {
return function (...args) {
console.log(`calling ${fn.name} with`, args);
const result = fn(...args);
console.log(`${fn.name} returned`, result);
return result;
};
}