Design Patterns in JavaScript
Design patterns are proven solutions to common programming problems. They make code more maintainable, scalable, and understandable. These patterns are used in every framework and library.
Common Patterns
- Module Pattern — Encapsulate code with private/public access using closures or ES modules
- Singleton — Only one instance exists: database connection, app config, store
- Observer — Subscribe to events: addEventListener, EventEmitter, Redux store
- Factory — Create objects without specifying exact class: createUser('admin') returns AdminUser
- MVC — Model-View-Controller: separate data (Model), display (View), and logic (Controller)
- Pub/Sub — Loose coupling: components communicate via events without knowing each other
Design Patterns Code
// Singleton Pattern — only one instance
class Database {
static instance = null;
constructor() {
if (Database.instance) return Database.instance;
this.connection = "Connected to DB";
Database.instance = this;
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true (same instance!)
// Observer Pattern (Pub/Sub)
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event, data) {
(this.events[event] || []).forEach(cb => cb(data));
}
off(event, callback) {
this.events[event] = (this.events[event] || []).filter(cb => cb !== callback);
}
}
const emitter = new EventEmitter();
emitter.on("userLogin", (user) => console.log(`Welcome, ${user}!`));
emitter.on("userLogin", (user) => console.log(`Logging: ${user} logged in`));
emitter.emit("userLogin", "Alice");
// Factory Pattern
function createUser(type, name) {
const base = { name, createdAt: new Date().toISOString() };
switch (type) {
case "admin": return { ...base, role: "admin", permissions: ["read", "write", "delete"] };
case "editor": return { ...base, role: "editor", permissions: ["read", "write"] };
default: return { ...base, role: "viewer", permissions: ["read"] };
}
}
console.log(createUser("admin", "Alice"));
console.log(createUser("viewer", "Bob"));Tip
Tip
Learn one pattern at a time and apply it immediately. Observer pattern for event systems, Module pattern for encapsulation, and Singleton for configuration are the three most useful patterns for JavaScript developers.
Patterns = proven solutions to recurring problems
Common Mistake
Warning
Over-engineering with design patterns. Not every problem needs a pattern. A simple function often works better than a Factory pattern for creating 2-3 similar objects. Use patterns when the complexity justifies them.
Practice Task
Note
Design patterns: (1) Implement the Observer pattern for a simple event system. (2) Create a Singleton configuration manager. (3) Build a Factory function for creating different types of form fields.
Quick Quiz
Key Takeaways
- Design patterns are proven solutions to common programming problems.
- Module Pattern — Encapsulate code with private/public access using closures or ES modules
- Singleton — Only one instance exists: database connection, app config, store
- Observer — Subscribe to events: addEventListener, EventEmitter, Redux store