← Back to Blog
console.log()

JavaScript Design Patterns You Should Know

Master essential design patterns in JavaScript. Learn singleton, factory, observer, and more with practical examples.

javascriptpatternsarchitecture

JavaScript Design Patterns

Design patterns are proven solutions to common problems. Let's explore the most useful ones.

Singleton Pattern

Ensure only one instance exists:

class Database {
 static instance = null;

 constructor() {
 if (Database.instance) {
 return Database.instance;
 }
 this.connection = this.connect();
 Database.instance = this;
 }

 connect() {
 console.log('Connecting to database...');
 return { connected: true };
 }
}

const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true

Factory Pattern

Create objects without specifying the exact class:

class UserFactory {
 static createUser(type) {
 switch (type) {
 case 'admin':
 return new AdminUser();
 case 'guest':
 return new GuestUser();
 default:
 return new RegularUser();
 }
 }
}

const admin = UserFactory.createUser('admin');
const guest = UserFactory.createUser('guest');

Observer Pattern

Subscribe to events:

class EventEmitter {
 constructor() {
 this.events = {};
 }

 on(event, callback) {
 if (!this.events[event]) {
 this.events[event] = [];
 }
 this.events[event].push(callback);
 }

 emit(event, data) {
 if (this.events[event]) {
 this.events[event].forEach(callback => callback(data));
 }
 }

 off(event, callback) {
 if (this.events[event]) {
 this.events[event] = this.events[event]
 .filter(cb => cb !== callback);
 }
 }
}

const emitter = new EventEmitter();
emitter.on('userCreated', user => console.log('New user:', user));
emitter.emit('userCreated', { name: 'John' });

Module Pattern

Encapsulate private state:

const Counter = (function() {
 let count = 0; // Private

 return {
 increment() {
 return ++count;
 },
 decrement() {
 return --count;
 },
 getCount() {
 return count;
 }
 };
})();

Counter.increment();
Counter.increment();
console.log(Counter.getCount()); // 2
console.log(Counter.count); // undefined

Strategy Pattern

Swap algorithms at runtime:

const paymentStrategies = {
 creditCard: (amount) => {
 console.log(`Paying ${amount} with credit card`);
 },
 paypal: (amount) => {
 console.log(`Paying ${amount} with PayPal`);
 },
 crypto: (amount) => {
 console.log(`Paying ${amount} with crypto`);
 }
};

function processPayment(amount, strategy) {
 paymentStrategies[strategy](amount);
}

processPayment(100, 'paypal');

Decorator Pattern

Add behavior dynamically:

function withLogging(fn) {
 return function(...args) {
 console.log(`Calling ${fn.name} with`, args);
 const result = fn.apply(this, args);
 console.log(`Result:`, result);
 return result;
 };
}

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3);

Conclusion

These patterns solve real problems. Learn them, but don't force them where they don't fit.