Gyaan

Design Patterns

advanced design-patterns module singleton observer factory

Design patterns are reusable solutions to common problems. We don’t need to memorize all of them, but knowing the four most common ones in JavaScript will come up in interviews and help us write better code.

Module Pattern

Uses closures to create private variables and expose only a public API. Before ES Modules existed, this was the go-to way to avoid polluting the global scope.

const Counter = (function() {
  // Private — can't be accessed from outside
  let count = 0;

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

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

The IIFE runs once and returns an object. The returned methods form a closure over count, so they can access it but nobody else can. jQuery used this pattern extensively.

Singleton Pattern

Ensures only one instance of something exists. Useful for things like a database connection, a logger, or a global store.

const Database = (function() {
  let instance;

  function createInstance() {
    return {
      host: 'localhost',
      query(sql) { console.log(`Running: ${sql}`); }
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true (same instance)

The first call to getInstance() creates the object. Every subsequent call returns that same object. Redux store is a singleton — there’s only one store for the entire app.

Observer Pattern

Also known as Pub/Sub (publish/subscribe). One object (the subject) maintains a list of dependents (observers) and notifies them when something changes.

function createEventEmitter() {
  const listeners = {};

  return {
    on(event, callback) {
      if (!listeners[event]) listeners[event] = [];
      listeners[event].push(callback);
    },
    emit(event, data) {
      (listeners[event] || []).forEach(cb => cb(data));
    },
    off(event, callback) {
      if (!listeners[event]) return;
      listeners[event] = listeners[event].filter(cb => cb !== callback);
    }
  };
}

const emitter = createEventEmitter();
const handler = data => console.log('Got:', data);

emitter.on('message', handler);
emitter.emit('message', 'Hello!');  // Got: Hello!
emitter.off('message', handler);
emitter.emit('message', 'Hello?');  // (nothing — handler was removed)

This pattern is everywhere. addEventListener in the browser is an observer. Node.js EventEmitter, RxJS, and Vue’s reactivity system all use variations of this pattern.

Factory Pattern

A function that creates and returns objects without using new. Useful when we need to create many similar objects with slight variations.

function createUser(name, role) {
  return {
    name,
    role,
    permissions: role === 'admin'
      ? ['read', 'write', 'delete']
      : ['read'],
    describe() {
      return `${this.name} (${this.role})`;
    }
  };
}

const admin = createUser('Manish', 'admin');
const viewer = createUser('Pika', 'viewer');

console.log(admin.permissions); // ['read', 'write', 'delete']
console.log(viewer.permissions); // ['read']

The factory hides the creation logic. The caller doesn’t care how the object is built — they just say what they want. React.createElement() is a factory. Express’s express() call is also a factory that creates an app instance.

Quick Reference

PatternCore IdeaReal-World Example
ModulePrivate state via closuresjQuery, old-school JS libraries
SingletonOne instance, global accessRedux store, DB connections
ObserverSubscribe to changesaddEventListener, Node EventEmitter
FactoryFunction creates objectsReact.createElement, express()

In simple language, these patterns aren’t rules we have to follow — they’re proven solutions that people have found useful over time. Knowing them helps us recognize patterns in existing code and write cleaner solutions ourselves.