| title | Design Patterns |
|---|---|
| sidebarTitle | Design Patterns: Reusable Solutions |
| description | Learn JavaScript design patterns like Module, Singleton, Observer, Factory, Proxy, and Decorator. Understand when to use each pattern and avoid common pitfalls. |
| og:type | article |
| article:author | Leonardo Maldonado |
| article:section | Advanced Topics |
| article:tag | design patterns, singleton, observer, factory, proxy, decorator, software design |
Ever find yourself solving the same problem over and over? What if experienced developers already figured out the best solutions to these recurring challenges?
// The Observer pattern — notify multiple listeners when something happens
const newsletter = {
subscribers: [],
subscribe(callback) {
this.subscribers.push(callback)
},
publish(article) {
this.subscribers.forEach(callback => callback(article))
}
}
// Anyone can subscribe
newsletter.subscribe(article => console.log(`New article: ${article}`))
newsletter.subscribe(article => console.log(`Saving "${article}" for later`))
// When we publish, all subscribers get notified
newsletter.publish("Design Patterns in JavaScript")
// "New article: Design Patterns in JavaScript"
// "Saving "Design Patterns in JavaScript" for later"Design patterns are proven solutions to common problems in software design. They're not code you copy-paste. They're templates, blueprints, or recipes that you adapt to solve specific problems in your own code. Learning patterns gives you a vocabulary to discuss solutions with other developers and helps you recognize when a well-known solution fits your problem.
**What you'll learn in this guide:** - What design patterns are and why they matter - The Module pattern for organizing code with private state - The Singleton pattern (and why it's often unnecessary in JavaScript) - The Factory pattern for creating objects dynamically - The Observer pattern for event-driven programming - The Proxy pattern for controlling object access - The Decorator pattern for adding behavior without modification - How to choose the right pattern for your problem **Prerequisites:** This guide assumes you understand [Factories and Classes](/concepts/factories-classes) and [IIFE, Modules & Namespaces](/concepts/iife-modules). Design patterns build on these object-oriented and modular programming concepts.Think of design patterns like specialized tools in a toolkit. A general-purpose hammer works for many tasks, but sometimes you need a specific tool: a Phillips screwdriver for certain screws, a wrench for bolts, or pliers for gripping.
┌─────────────────────────────────────────────────────────────────────────┐
│ DESIGN PATTERNS TOOLKIT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ CREATIONAL STRUCTURAL BEHAVIORAL │
│ ─────────── ────────── ────────── │
│ How objects How objects How objects │
│ are created are composed communicate │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Singleton │ │ Proxy │ │ Observer │ │
│ │ Factory │ │ Decorator │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Use when you need Use when you need Use when objects │
│ to control object to wrap or extend need to react to │
│ creation objects changes in others │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ MODULE (JS-specific) — Encapsulates code with private state │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
You don't use every tool for every job. Similarly, you don't use every pattern in every project. The skill is recognizing when a pattern fits your problem.
Design patterns are typical solutions to commonly occurring problems in software design. The term was popularized by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides — the "Gang of Four" (GoF) — in their 1994 book Design Patterns: Elements of Reusable Object-Oriented Software. They catalogued 23 patterns that developers kept reinventing, and the book has sold over 500,000 copies worldwide.
The original GoF patterns were written for languages like C++ and Smalltalk. As Addy Osmani explains in Learning JavaScript Design Patterns, JavaScript is fundamentally different:
| Feature | Impact on Patterns |
|---|---|
| First-class functions | Many patterns simplify to just passing functions around |
| Prototypal inheritance | No need for complex class hierarchies |
| ES Modules | Built-in module system replaces manual Module pattern |
| Dynamic typing | No need for interface abstractions |
| Closures | Natural way to create private state |
This means some classical patterns are overkill in JavaScript, while others become more elegant. We'll focus on the patterns that are genuinely useful in modern JavaScript.
The original GoF patterns are grouped into three categories:
-
Creational Patterns — Control how objects are created
- Singleton, Factory Method, Abstract Factory, Builder, Prototype
-
Structural Patterns — Control how objects are composed
- Proxy, Decorator, Adapter, Facade, Bridge, Composite, Flyweight
-
Behavioral Patterns — Control how objects communicate
- Observer, Strategy, Command, Mediator, Iterator, State, and others
We'll cover six patterns that are particularly useful in JavaScript: Module (JS-specific), Singleton, Factory, Observer, Proxy, and Decorator.
The Module pattern encapsulates code into reusable units with private and public parts. Before ES6 modules existed, developers used IIFEs (Immediately Invoked Function Expressions) to create this pattern. Today, JavaScript has built-in ES Modules that provide this naturally.
Each file is its own module. Variables are private unless you export them:
// counter.js — A module with private state
let count = 0 // Private — not exported, not accessible outside
export function increment() {
count++
return count
}
export function decrement() {
count--
return count
}
export function getCount() {
return count
}
// main.js — Using the module
import { increment, getCount } from './counter.js'
increment()
increment()
console.log(getCount()) // 2
// Trying to access private state
// console.log(count) // ReferenceError: count is not definedBefore ES6, developers used closures to create modules:
// The revealing module pattern using IIFE
const Counter = (function() {
// Private variables and functions
let count = 0
function logChange(action) {
console.log(`Counter ${action}: ${count}`)
}
// Public API — "revealed" by returning an object
return {
increment() {
count++
logChange('incremented')
return count
},
decrement() {
count--
logChange('decremented')
return count
},
getCount() {
return count
}
}
})()
Counter.increment() // "Counter incremented: 1"
Counter.increment() // "Counter incremented: 2"
console.log(Counter.getCount()) // 2
// Private members are truly private
console.log(Counter.count) // undefined
console.log(Counter.logChange) // undefinedThe Singleton pattern ensures a class has only one instance and provides a global access point to that instance. According to Refactoring Guru, it solves two problems: guaranteeing a single instance and providing global access to it.
// Singleton using Object.freeze — immutable configuration
const Config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
debug: false
}
Object.freeze(Config) // Prevent all modifications
// Usage anywhere in your app
console.log(Config.apiUrl) // "https://api.example.com"
// Attempting to modify throws an error in strict mode (silently fails otherwise)
Config.apiUrl = 'https://evil.com'
console.log(Config.apiUrl) // Still "https://api.example.com"
Config.debug = true
console.log(Config.debug) // Still false — frozen objects are immutablelet instance = null
class Database {
constructor() {
if (instance) {
return instance // Return existing instance
}
this.connection = null
instance = this
}
connect(url) {
if (!this.connection) {
this.connection = `Connected to ${url}`
console.log(this.connection)
}
return this.connection
}
}
const db1 = new Database()
const db2 = new Database()
console.log(db1 === db2) // true — Same instance!
db1.connect('mongodb://localhost') // "Connected to mongodb://localhost"
db2.connect('mongodb://other') // Returns same connection, doesn't reconnectHere's the thing: Singletons are often unnecessary in JavaScript. Here's why:
// ES Modules are already singletons!
// config.js
export const config = {
apiUrl: 'https://api.example.com',
timeout: 5000
}
// main.js
import { config } from './config.js'
// other.js
import { config } from './config.js'
// Both files get the SAME object — modules are cached!- Testing difficulties — Tests share the same instance, making isolation hard
- Hidden dependencies — Code that uses a Singleton has an implicit dependency
- Tight coupling — Components become coupled to a specific implementation
- ES Modules already do this — Module exports are cached; you get the same object every time
Better alternatives: Dependency injection, React Context, or simply exporting an object from a module.
Despite the caveats, Singletons can be appropriate for:
- Logging services — One logger instance for the entire app
- Configuration objects — App-wide settings that shouldn't change
- Connection pools — Managing a single pool of database connections
The Factory pattern creates objects without exposing the creation logic. Instead of using new directly, you call a factory function that returns the appropriate object. According to the State of JS 2023 survey, factory functions are among the most commonly used patterns in modern JavaScript codebases. This centralizes object creation and makes it easy to change how objects are created without updating every call site.
// Factory function — creates different user types
function createUser(type, name) {
const baseUser = {
name,
createdAt: new Date(),
greet() {
return `Hi, I'm ${this.name}`
}
}
switch (type) {
case 'admin':
return {
...baseUser,
role: 'admin',
permissions: ['read', 'write', 'delete', 'manage-users'],
promote(user) {
console.log(`${this.name} promoted ${user.name}`)
}
}
case 'editor':
return {
...baseUser,
role: 'editor',
permissions: ['read', 'write']
}
case 'viewer':
default:
return {
...baseUser,
role: 'viewer',
permissions: ['read']
}
}
}
// Usage — no need to know the internal structure
const admin = createUser('admin', 'Alice')
const editor = createUser('editor', 'Bob')
const viewer = createUser('viewer', 'Charlie')
console.log(admin.permissions) // ['read', 'write', 'delete', 'manage-users']
console.log(editor.permissions) // ['read', 'write']
console.log(viewer.greet()) // "Hi, I'm Charlie"- Creating objects with complex setup — Encapsulate the complexity
- Creating different types based on input — Switch logic in one place
- Decoupling creation from usage — Callers don't need to know implementation details
The Observer pattern defines a subscription mechanism that notifies multiple objects about events. According to Refactoring Guru, it lets you "define a subscription mechanism to notify multiple objects about any events that happen to the object they're observing."
This pattern is everywhere: DOM events, React state updates, Redux subscriptions, Node.js EventEmitter, and RxJS observables all use variations of Observer.
class Observable {
constructor() {
this.observers = []
}
subscribe(fn) {
this.observers.push(fn)
// Return an unsubscribe function
return () => {
this.observers = this.observers.filter(observer => observer !== fn)
}
}
notify(data) {
this.observers.forEach(observer => observer(data))
}
}
// Usage: A stock price tracker
const stockPrice = new Observable()
// Subscriber 1: Log to console
const unsubscribeLogger = stockPrice.subscribe(price => {
console.log(`Stock price updated: $${price}`)
})
// Subscriber 2: Check for alerts
stockPrice.subscribe(price => {
if (price > 150) {
console.log('ALERT: Price above $150!')
}
})
// Subscriber 3: Update UI (simulated)
stockPrice.subscribe(price => {
console.log(`Updating chart with price: $${price}`)
})
// When price changes, all subscribers are notified
stockPrice.notify(145)
// "Stock price updated: $145"
// "Updating chart with price: $145"
stockPrice.notify(155)
// "Stock price updated: $155"
// "ALERT: Price above $150!"
// "Updating chart with price: $155"
// Unsubscribe the logger
unsubscribeLogger()
stockPrice.notify(160)
// No log message, but alert and chart still update┌─────────────────────────────────────────────────────────────────────────┐
│ THE OBSERVER PATTERN │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ PUBLISHER (Observable) SUBSCRIBERS (Observers) │
│ ────────────────────── ─────────────────────── │
│ │
│ ┌─────────────────────┐ ┌─────────────┐ │
│ │ │ ──────────► │ Reader #1 │ │
│ │ Magazine │ └─────────────┘ │
│ │ Publisher │ ┌─────────────┐ │
│ │ │ ──────────► │ Reader #2 │ │
│ │ • subscribers[] │ └─────────────┘ │
│ │ • subscribe() │ ┌─────────────┐ │
│ │ • unsubscribe() │ ──────────► │ Reader #3 │ │
│ │ • notify() │ └─────────────┘ │
│ │ │ │
│ └─────────────────────┘ │
│ │
│ When a new issue publishes, all subscribers receive it automatically. │
│ Readers can subscribe or unsubscribe at any time. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
// Observable form field
class FormField {
constructor(initialValue = '') {
this.value = initialValue
this.observers = []
}
subscribe(fn) {
this.observers.push(fn)
return () => {
this.observers = this.observers.filter(o => o !== fn)
}
}
setValue(newValue) {
this.value = newValue
this.observers.forEach(fn => fn(newValue))
}
}
// Usage
const emailField = new FormField('')
// Validator subscriber
emailField.subscribe(value => {
const isValid = value.includes('@')
console.log(isValid ? 'Valid email' : 'Invalid email')
})
// Character counter subscriber
emailField.subscribe(value => {
console.log(`Characters: ${value.length}`)
})
emailField.setValue('test')
// "Invalid email"
// "Characters: 4"
emailField.setValue('test@example.com')
// "Valid email"
// "Characters: 16"The Proxy pattern provides a surrogate or placeholder for another object to control access to it. In JavaScript, the ES6 Proxy object lets you intercept and redefine fundamental operations like property access, assignment, and function calls.
const user = {
name: 'Alice',
age: 25,
email: 'alice@example.com'
}
const userProxy = new Proxy(user, {
// Intercept property reads
get(target, property) {
console.log(`Accessing property: ${property}`)
return target[property]
},
// Intercept property writes
set(target, property, value) {
console.log(`Setting ${property} to ${value}`)
// Validation: age must be a non-negative number
if (property === 'age') {
if (typeof value !== 'number' || value < 0) {
throw new Error('Age must be a non-negative number')
}
}
// Validation: email must contain @
if (property === 'email') {
if (!value.includes('@')) {
throw new Error('Invalid email format')
}
}
target[property] = value
return true
}
})
// All access goes through the proxy
console.log(userProxy.name)
// "Accessing property: name"
// "Alice"
userProxy.age = 26
// "Setting age to 26"
userProxy.age = -5
// Error: Age must be a non-negative number
userProxy.email = 'invalid'
// Error: Invalid email format// Expensive object that we don't want to create until needed
function createExpensiveResource() {
console.log('Creating expensive resource...')
return {
data: 'Loaded data from database',
process() {
return `Processing: ${this.data}`
}
}
}
// Proxy that delays creation until first use
function createLazyResource() {
let resource = null
return new Proxy({}, {
get(target, property) {
// Create resource on first access
if (!resource) {
resource = createExpensiveResource()
}
const value = resource[property]
// If it's a method, bind it to the resource
return typeof value === 'function' ? value.bind(resource) : value
}
})
}
const lazyResource = createLazyResource()
console.log('Proxy created, resource not loaded yet')
// Resource is only created when we actually use it
console.log(lazyResource.data)
// "Creating expensive resource..."
// "Loaded data from database"
console.log(lazyResource.process())
// "Processing: Loaded data from database"| Use Case | Example |
|---|---|
| Validation | Validate data before setting properties |
| Logging/Debugging | Log all property accesses for debugging |
| Lazy initialization | Delay expensive object creation |
| Access control | Restrict access to certain properties |
| Caching | Cache expensive computations |
The Decorator pattern attaches new behaviors to objects by wrapping them in objects that contain these behaviors. According to Refactoring Guru, it lets you "attach new behaviors to objects by placing these objects inside special wrapper objects."
In JavaScript, decorators are often implemented as functions that take an object and return an enhanced version.
// Base object
const createCharacter = (name) => ({
name,
health: 100,
describe() {
return `${this.name} (${this.health} HP)`
}
})
// Decorator: Add flying ability
const withFlying = (character) => ({
...character,
fly() {
return `${character.name} soars through the sky!`
},
describe() {
return `${character.describe()} [Can fly]`
}
})
// Decorator: Add swimming ability
const withSwimming = (character) => ({
...character,
swim() {
return `${character.name} dives into the water!`
},
describe() {
return `${character.describe()} [Can swim]`
}
})
// Decorator: Add armor
const withArmor = (character, armorPoints) => ({
...character,
armor: armorPoints,
takeDamage(amount) {
const reducedDamage = Math.max(0, amount - armorPoints)
character.health -= reducedDamage
return `${character.name} takes ${reducedDamage} damage (${armorPoints} blocked)`
},
describe() {
return `${character.describe()} [Armor: ${armorPoints}]`
}
})
// Compose decorators to build characters
const duck = withSwimming(withFlying(createCharacter('Duck')))
console.log(duck.describe()) // "Duck (100 HP) [Can fly] [Can swim]"
console.log(duck.fly()) // "Duck soars through the sky!"
console.log(duck.swim()) // "Duck dives into the water!"
const knight = withArmor(createCharacter('Knight'), 20)
console.log(knight.describe()) // "Knight (100 HP) [Armor: 20]"
console.log(knight.takeDamage(50)) // "Knight takes 30 damage (20 blocked)"Decorators also work great with functions:
// Decorator: Log function calls
const withLogging = (fn, fnName) => {
return function(...args) {
console.log(`Calling ${fnName} with:`, args)
const result = fn.apply(this, args)
console.log(`${fnName} returned:`, result)
return result
}
}
// Decorator: Memoize (cache) results
const withMemoization = (fn) => {
const cache = new Map()
return function(...args) {
const key = JSON.stringify(args)
if (cache.has(key)) {
console.log('Cache hit!')
return cache.get(key)
}
const result = fn.apply(this, args)
cache.set(key, result)
return result
}
}
// Original function
function fibonacci(n) {
if (n <= 1) return n
return fibonacci(n - 1) + fibonacci(n - 2)
}
// Decorated version with logging
const loggedAdd = withLogging((a, b) => a + b, 'add')
loggedAdd(2, 3)
// "Calling add with: [2, 3]"
// "add returned: 5"
// Decorated fibonacci with memoization
const memoizedFib = withMemoization(function fib(n) {
if (n <= 1) return n
return memoizedFib(n - 1) + memoizedFib(n - 2)
})
console.log(memoizedFib(10)) // 55
console.log(memoizedFib(10)) // "Cache hit!" — 55- Adding features without modifying original code — Open/Closed Principle
- Composing behaviors dynamically — Mix and match capabilities
- Cross-cutting concerns — Logging, caching, validation, timing
┌─────────────────────────────────────────────────────────────────────────┐
│ DESIGN PATTERN MISTAKES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ MISTAKE #1: PATTERN OVERUSE │
│ ─────────────────────────── │
│ Using patterns where simple code would work better. │
│ A plain function is often better than a Factory class. │
│ │
│ MISTAKE #2: WRONG PATTERN CHOICE │
│ ───────────────────────────── │
│ Using Singleton when you just need a module export. │
│ Using Observer when a simple callback would suffice. │
│ │
│ MISTAKE #3: IGNORING JAVASCRIPT IDIOMS │
│ ──────────────────────────────────── │
│ JavaScript has closures, first-class functions, and ES modules. │
│ Many classical patterns simplify dramatically in JavaScript. │
│ │
│ MISTAKE #4: PREMATURE ABSTRACTION │
│ ──────────────────────────────── │
│ Adding patterns before you have a real problem to solve. │
│ "You Ain't Gonna Need It" (YAGNI) applies to patterns too. │
│ │
└─────────────────────────────────────────────────────────────────────────┘
When you learn a new pattern, resist the urge to use it everywhere:
// ❌ OVERKILL: Factory for simple objects
class UserFactory {
createUser(name) {
return new User(name)
}
}
const factory = new UserFactory()
const user = factory.createUser('Alice')
// ✓ SIMPLE: Just create the object
const user = { name: 'Alice' }
// or
const user = new User('Alice')- Do I have a real problem? Don't solve problems you don't have yet.
- Is there a simpler solution? A plain function or object might be enough.
- Does JavaScript already solve this? ES modules, Promises, and iterators are built-in patterns.
- Will my team understand it? Patterns only help if everyone knows them.
| Problem | Pattern | Alternative |
|---|---|---|
| Need to organize code with private state | Module | ES6 module exports |
| Need exactly one instance | Singleton | Just export an object from a module |
| Need to create objects dynamically | Factory | Plain function returning objects |
| Need to notify multiple listeners of changes | Observer | EventEmitter, callbacks, or a library |
| Need to control or validate object access | Proxy | Getter/setter methods |
| Need to add behavior without modification | Decorator | Higher-order functions, composition |
**The key things to remember:**
-
Design patterns are templates, not code — Adapt them to your specific problem; don't force-fit them
-
JavaScript simplifies many patterns — First-class functions, closures, and ES modules reduce boilerplate
-
Module pattern organizes code — Use ES modules for new projects; understand IIFE pattern for legacy code
-
Singleton is often unnecessary — ES module exports are already cached; use sparingly if at all
-
Factory centralizes object creation — Great for creating different types based on input
-
Observer enables event-driven code — The foundation of DOM events, React state, and reactive programming
-
Proxy intercepts object operations — Use for validation, logging, lazy loading, and access control
-
Decorator adds behavior through wrapping — Compose features without modifying original code
-
Avoid pattern overuse — Simple code beats clever patterns; apply the YAGNI principle
-
Learn to recognize patterns in the wild — DOM events use Observer, Promises use a form of Observer, middleware uses Decorator
**Answer:**
The Module pattern encapsulates code into reusable units with **private and public parts**. It allows you to:
- Hide implementation details (private variables and functions)
- Expose only a public API
- Avoid polluting the global namespace
In modern JavaScript, ES6 modules (`import`/`export`) provide this naturally. Variables in a module are private unless exported.
```javascript
// privateHelper is not exported — it's private
function privateHelper() { /* ... */ }
// Only publicFunction is accessible to importers
export function publicFunction() {
privateHelper()
}
```
Singleton is often unnecessary in JavaScript because:
1. **ES modules are already singletons** — When you export an object, all importers get the same instance
2. **Testing difficulties** — Tests share state, making isolation hard
3. **Hidden dependencies** — Code using Singletons has implicit dependencies
4. **JavaScript can create objects directly** — No need for the class-based workarounds other languages require
```javascript
// ES module — already a singleton!
export const config = { apiUrl: '...' }
// Every import gets the same object
import { config } from './config.js' // Same instance everywhere
```
The Observer pattern has three key parts:
1. **Subscriber list** — An array to store observer functions
2. **Subscribe method** — Adds a function to the list (often returns an unsubscribe function)
3. **Notify method** — Calls all subscribed functions with data
```javascript
class Observable {
constructor() {
this.observers = [] // 1. Subscriber list
}
subscribe(fn) { // 2. Subscribe method
this.observers.push(fn)
return () => { // Returns unsubscribe
this.observers = this.observers.filter(o => o !== fn)
}
}
notify(data) { // 3. Notify method
this.observers.forEach(fn => fn(data))
}
}
```
Both wrap objects, but they have different purposes:
**Proxy Pattern:**
- **Controls access** to an object
- Intercepts operations like get, set, delete
- The proxy typically has the same interface as the target
- Use for: validation, logging, lazy loading, access control
**Decorator Pattern:**
- **Adds new behavior** to an object
- Wraps the object and extends its capabilities
- May add new methods or modify existing ones
- Use for: composing features, cross-cutting concerns
```javascript
// Proxy — same interface, controlled access
const proxy = new Proxy(obj, { get(t, p) { /* intercept */ } })
// Decorator — enhanced interface, new behavior
const enhanced = withLogging(withCache(obj))
```
Use the Factory pattern when:
1. **Object creation is complex** — Encapsulate setup logic in one place
2. **You need different types based on input** — Switch logic centralized in the factory
3. **You want to decouple creation from usage** — Callers don't need to know implementation
4. **You might change how objects are created** — Update the factory, not every call site
```javascript
// Factory — creation logic in one place
function createNotification(type, message) {
switch (type) {
case 'error': return { type, message, color: 'red' }
case 'success': return { type, message, color: 'green' }
default: return { type: 'info', message, color: 'blue' }
}
}
// Easy to use — no need to know the structure
const notification = createNotification('error', 'Something went wrong')
```
The "Golden Hammer" anti-pattern is the tendency to use a familiar tool (or pattern) for every problem, even when it's not appropriate.
**Signs you're doing this:**
- Using Singleton for everything that "should be global"
- Creating Factory classes for simple object literals
- Using Observer when a callback would suffice
- Adding patterns before you have a real problem
**How to avoid it:**
- Start with the simplest solution
- Add patterns only when you hit a real problem they solve
- Ask: "Would a plain function/object work here?"
- Remember: Code clarity beats clever patterns
Design patterns are reusable solutions to common problems in software design. They are not specific code implementations but templates you adapt to your needs. In JavaScript, many classical patterns simplify because the language has first-class functions, closures, and prototypal inheritance, which reduce the need for complex class hierarchies. The Observer pattern lets an object (the subject) notify a list of dependents (observers) when its state changes. Use it when multiple parts of your application need to react to the same event. The DOM's `addEventListener` is a built-in Observer implementation, and frameworks like React and Vue use Observer-like patterns internally for reactivity. No. As Addy Osmani notes in *Learning JavaScript Design Patterns*, many GoF patterns were designed for statically typed languages like C++ and Java. JavaScript's first-class functions, closures, and dynamic typing make patterns like Strategy, Command, and Iterator trivial — often just a function or callback. Focus on patterns that solve real problems in your codebase. ES Modules are already singletons — a module's exports are cached after the first import, so every file gets the same object. Using a Singleton class adds complexity without benefit. Singletons also make testing harder because components share hidden global state. Prefer dependency injection or module exports instead. The Proxy pattern controls access to an object without changing its interface — it intercepts operations like property reads or function calls. The Decorator pattern adds new behavior or capabilities to an object by wrapping it. JavaScript's built-in `Proxy` object implements the Proxy pattern natively since ES2015.
Deep dive into object creation with factory functions and ES6 classes How JavaScript evolved from IIFEs to modern ES modules Functions that work with functions — the foundation of many patterns How closures enable private state in the Module pattern
Complete API reference for JavaScript's built-in Proxy object Official guide to ES6 modules with import and export Complete catalog of classic design patterns with examples The Reflect object used with Proxy for default behavior Lydia Hallie and Addy Osmani's modern guide with animated visualizations. Each pattern gets its own interactive explanation showing exactly how data flows. Beginner-friendly walkthrough of essential patterns with practical code examples. Great starting point if you're new to design patterns. Addy Osmani's free online book, updated for modern JavaScript. The definitive resource covering patterns, anti-patterns, and real-world applications. Authoritative guide on adding behaviors to classes without inheritance. Shows the EventMixin pattern that's used throughout JavaScript libraries. Fireship's fast-paced overview covering the essential patterns every developer should know. Perfect for a quick refresher or introduction. Free comprehensive course covering MVC, MVP, and organizing large JavaScript applications. Great for understanding patterns in context. Fun Fun Function's engaging explanation of factories vs classes. MPJ's conversational style makes complex concepts approachable.