Academic Block

Design Patterns (Module / Observer) Get Certified

Design patterns are reusable solutions to common problems in software design. Two of the most useful in everyday JavaScript are the Module Pattern (for hiding implementation details) and the Observer Pattern (for letting different parts of an app react to events without being tightly coupled).

The Module Pattern

What this code does: Builds a counterModule with an IIFE and a closure. The count variable stays private. We increment twice, decrement once, then print the final count. Trying to access count directly from outside returns undefined.

const counterModule = (function () {
    // Private state — not accessible from outside
    let count = 0;

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

    // Only these three are exposed publicly
    return {
        increment,
        decrement,
        getCount
    };
})();

console.log("Start:", counterModule.getCount());
counterModule.increment();
counterModule.increment();
counterModule.decrement();
console.log("After changes:", counterModule.getCount());
console.log("Direct access to count:", counterModule.count);

The Revealing Module Pattern

What this code does: Creates a cartModule using the Revealing Module Pattern. We add three items, print the count and total, then remove the Mouse and print the new total.

const cartModule = (function () {
    let items = [];

    function addItem(name, price) {
        items.push({ name, price });
    }
    function removeItem(name) {
        items = items.filter((item) => item.name !== name);
    }
    function getTotal() {
        return items.reduce((sum, item) => sum + item.price, 0);
    }
    function getItemCount() {
        return items.length;
    }

    // Revealed public API
    return {
        add: addItem,
        remove: removeItem,
        total: getTotal,
        count: getItemCount
    };
})();

cartModule.add("Keyboard", 45);
cartModule.add("Mouse", 20);
cartModule.add("Monitor", 150);
console.log("Items in cart:", cartModule.count());
console.log("Total:", cartModule.total());
cartModule.remove("Mouse");
console.log("After removing Mouse — total:", cartModule.total());

The Observer Pattern

What this code does: Builds a small EventEmitter. We subscribe two listeners to the “orderPlaced” event, emit an order, then unsubscribe the email listener and emit again so only the logger still reacts.

class EventEmitter {
    #listeners = {};

    on(eventName, callback) {
        if (!this.#listeners[eventName]) {
            this.#listeners[eventName] = [];
        }
        this.#listeners[eventName].push(callback);
    }

    off(eventName, callback) {
        if (!this.#listeners[eventName]) return;
        this.#listeners[eventName] =
            this.#listeners[eventName].filter((fn) => fn !== callback);
    }

    emit(eventName, data) {
        if (!this.#listeners[eventName]) return;
        this.#listeners[eventName].forEach((callback) => callback(data));
    }
}

const emitter = new EventEmitter();

function logOrder(order) {
    console.log("Logger heard order:", order.id);
}
function sendConfirmationEmail(order) {
    console.log("Emailing confirmation for order:", order.id);
}

emitter.on("orderPlaced", logOrder);
emitter.on("orderPlaced", sendConfirmationEmail);

emitter.emit("orderPlaced", { id: 1001 });

emitter.off("orderPlaced", sendConfirmationEmail);
console.log("--- after unsubscribing the email listener ---");
emitter.emit("orderPlaced", { id: 1002 });

Combining the Module and Observer Patterns

What this code does: Creates a themeStore module that keeps its state private. Two independent subscribers react when we change the theme from “light” to “dark”.

const themeStore = (function () {
    let currentTheme = "light";
    const listeners = [];

    function subscribe(callback) {
        listeners.push(callback);
    }
    function setTheme(newTheme) {
        if (newTheme === currentTheme) return;
        currentTheme = newTheme;
        listeners.forEach((callback) => callback(currentTheme));
    }
    function getTheme() {
        return currentTheme;
    }

    return {
        subscribe,
        setTheme,
        getTheme
    };
})();

// Different parts of an app subscribing independently
themeStore.subscribe((theme) => {
    console.log("Header component: switching to", theme, "theme");
});
themeStore.subscribe((theme) => {
    console.log("Sidebar component: switching to", theme, "theme");
});

console.log("Initial theme:", themeStore.getTheme());
themeStore.setTheme("dark");

Module vs Observer at a Glance

Aspect Module Pattern Observer Pattern
Main purpose Encapsulate state, hide internals Let parts of the app react to events
Built with Closures (IIFE) or class private fields A list of callbacks + emit/notify logic
Coupling Consumers depend on a public API Subject and observers stay decoupled
Typical use Utilities, stores, services Events, pub/sub, state changes

Common Mistakes

  • Exposing internal state directly from a module
  • Forgetting to provide an unsubscribe / off method
  • Letting one failing observer callback stop the rest
  • Losing this inside observer callbacks
  • Overusing IIFEs when modern classes or ES modules are clearer

Best Practices

  • Keep a module’s public API small and intentional
  • Prefer class private fields or ES modules in new code
  • Always provide a way to unsubscribe from observers
  • Wrap observer callbacks in try/catch so one failure doesn’t break others
  • Use the Observer Pattern only when parts of the app truly need to stay decoupled

Quick Summary

  • The Module Pattern hides internal state and exposes only a controlled public interface
  • The Revealing Module Pattern defines everything privately first, then reveals the public API clearly
  • The Observer Pattern lets a subject notify many listeners without knowing who they are
  • Combining both lets a module keep state private while still notifying the outside world of changes

Try It Yourself

What this code does: Creates a notificationCenter module. Two subscribers react when we send two notifications. At the end we print the full list of notifications that were stored privately.

const notificationCenter = (function () {
    let notifications = [];
    const listeners = [];

    function subscribe(callback) {
        listeners.push(callback);
    }
    function notify(message) {
        const entry = { id: notifications.length + 1, message };
        notifications.push(entry);
        listeners.forEach((callback) => callback(entry));
    }
    function getAll() {
        return [...notifications];
    }

    return {
        subscribe,
        notify,
        getAll
    };
})();

notificationCenter.subscribe((entry) => {
    console.log("UI badge: new notification —", entry.message);
});
notificationCenter.subscribe((entry) => {
    console.log("Analytics: logging notification #" + entry.id);
});

notificationCenter.notify("Your order has shipped");
notificationCenter.notify("Payment received");
console.log("All notifications:", notificationCenter.getAll());
Tip: Reach for the Module Pattern when you ask “what should stay hidden?” and the Observer Pattern when you ask “who else needs to know when this changes?”

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close