Skip to content
Home JavaScript JavaScript Design Patterns Explained — Singleton, Observer, Factory and More

JavaScript Design Patterns Explained — Singleton, Observer, Factory and More

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Advanced JS → Topic 17 of 27
Master JavaScript design patterns with deep dives into Singleton, Observer, Factory, and Module.
🔥 Advanced — solid JavaScript foundation required
In this tutorial, you'll learn
Master JavaScript design patterns with deep dives into Singleton, Observer, Factory, and Module.
  • Design patterns are templates for solving architectural problems, not rigid rules.
  • Creational patterns (Factory, Singleton) focus on efficient and controlled object instantiation.
  • Behavioral patterns (Observer, Strategy) manage communication between disparate objects.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer

Imagine you're building with LEGO. Instead of figuring out from scratch how to build a car every time, you follow a proven blueprint. Design patterns are those blueprints — battle-tested solutions to problems that developers have already solved a thousand times before. You don't copy the blueprint exactly, you adapt it. The point isn't to memorise shapes — it's to recognise the problem and know which blueprint fits.

Every production JavaScript codebase eventually grows to a point where 'just making it work' stops being enough. Components start talking to each other in ways nobody planned. State changes ripple unpredictably through the app. A bug fix in one place breaks three things elsewhere. That's not bad luck — it's what happens when code lacks structural intent. Design patterns are the antidote. They're a shared vocabulary between developers and a set of proven architectural decisions that prevent entire categories of bugs before they're written.

The problem design patterns solve is coupling — the invisible glue that binds unrelated parts of your code together. When your PaymentService directly instantiates a Logger, and your Logger directly reads from Config, and your Config mutates global state — you've built a house of cards. Change one thing, rebuild everything. Patterns like Singleton, Observer, and Factory give you controlled, intentional relationships between components instead of accidental ones.

By the end of this article you'll be able to recognise which pattern fits a given problem, implement each one correctly in modern JavaScript (including ES2022+ class features), understand the performance and memory implications of each, and spot the subtle bugs that come from applying them carelessly. This isn't a glossary tour — it's a practical field guide you'll actually use.

The Creational Powerhouse: The Factory Pattern

The Factory pattern provides an interface for creating objects, but allows subclasses or a central logic unit to alter the type of objects that will be created. In JavaScript, this is exceptionally useful when your application needs to generate different types of components (like different UI buttons or database connectors) based on environment variables or user input without hardcoding new ClassName() throughout your logic.

io/thecodeforge/patterns/ForgeFactory.js · JAVASCRIPT
123456789101112131415161718192021222324252627282930
/**
 * io.thecodeforge - Factory Pattern Implementation
 */
class DatabaseConnector {
    constructor(config) {
        this.config = config;
    }
    connect() { throw new Error("Method 'connect()' must be implemented."); }
}

class PostgreSqlConnector extends DatabaseConnector {
    connect() { return `Connected to Postgres at ${this.config.host}`; }
}

class MongoDbConnector extends DatabaseConnector {
    connect() { return `Connected to MongoDB at ${this.config.uri}`; }
}

class DBFactory {
    static createConnection(type, options) {
        switch(type) {
            case 'SQL': return new PostgreSqlConnector(options);
            case 'NoSQL': return new MongoDbConnector(options);
            default: throw new Error("Unsupported DB type");
        }
    }
}

const db = DBFactory.createConnection('SQL', { host: 'localhost' });
console.log(db.connect());
▶ Output
Connected to Postgres at localhost
🔥Forge Tip: Decoupling
Using a Factory means your business logic doesn't need to know the specific class names of your connectors, making it much easier to swap providers later.

The Singleton Pattern: Ensuring a Single Source of Truth

A Singleton restricts a class to a single instance and provides a global point of access to it. While often criticized if overused as 'global state,' it is vital for resource-heavy objects like Database Pools or Global Configuration managers where multiple instances would cause memory leaks or race conditions.

io/thecodeforge/patterns/ForgeSingleton.js · JAVASCRIPT
12345678910111213141516171819202122232425
/**
 * io.thecodeforge - Modern ES6 Singleton using Static Private Fields
 */
class GlobalConfig {
    static #instance = null;

    constructor() {
        if (GlobalConfig.#instance) {
            return GlobalConfig.#instance;
        }
        this.settings = { theme: 'dark', version: '2.1.0' };
        GlobalConfig.#instance = this;
    }

    static getInstance() {
        if (!this.#instance) {
            this.#instance = new GlobalConfig();
        }
        return this.#instance;
    }
}

const configA = new GlobalConfig();
const configB = GlobalConfig.getInstance();
console.log(configA === configB);
▶ Output
true

The Behavioral King: The Observer Pattern

The Observer pattern (the core of 'Pub/Sub' logic) allows an object (the Subject) to notify a list of 'Observers' about state changes. This is how event listeners work in the browser and how frameworks like React and Vue trigger UI updates when state changes.

io/thecodeforge/patterns/ForgeObserver.js · JAVASCRIPT
12345678910111213141516171819202122
/**
 * io.thecodeforge - Simple Event Bus (Observer Pattern)
 */
class EventBus {
    constructor() {
        this.subscribers = {};
    }

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

    publish(event, data) {
        if (!this.subscribers[event]) return;
        this.subscribers[event].forEach(cb => cb(data));
    }
}

const forgeBus = new EventBus();
forgeBus.subscribe('USER_LOGIN', (user) => console.log(`Welcome, ${user}!`));
forgeBus.publish('USER_LOGIN', 'ForgeAdmin');
▶ Output
Welcome, ForgeAdmin!
PatternCategoryPrimary Use Case
SingletonCreationalShared state/resource (e.g., Database Pool, Config)
FactoryCreationalCreating objects without exposing creation logic
ObserverBehavioralEvent-driven updates (e.g., UI updates, WebSockets)
ModuleStructuralEncapsulation and private/public scope management
StrategyBehavioralSwapping algorithms at runtime (e.g., Payment gateways)

🎯 Key Takeaways

  • Design patterns are templates for solving architectural problems, not rigid rules.
  • Creational patterns (Factory, Singleton) focus on efficient and controlled object instantiation.
  • Behavioral patterns (Observer, Strategy) manage communication between disparate objects.
  • Modern JS syntax (Private fields #, static methods) makes implementing classic patterns much cleaner and more secure.
  • Always favor readability over pattern purity—patterns should simplify your code, not complicate it.

⚠ Common Mistakes to Avoid

    Over-Engineering: Don't use a Factory for a class that will only ever have one implementation. It adds unnecessary complexity.
    Global State Abuse: Singletons can make unit testing difficult because they preserve state between tests. Always provide a way to 'reset' your Singleton for testing environments.
    Memory Leaks in Observers: Forgetting to 'unsubscribe' from an event bus when a component is destroyed can lead to ghost listeners that consume memory and trigger unexpected logic.
    Ignoring Native Features: Don't manually build a Module pattern if standard ES Modules (import/export) already solve the encapsulation problem for you.

Interview Questions on This Topic

  • QExplain the 'Open/Closed Principle' and which design pattern helps implement it best.
  • QHow would you implement a Thread-Safe Singleton in a Node.js environment?
  • QDesign a 'Undo/Redo' system using the Command Pattern in JavaScript.
  • QWhat are the pros and cons of using the Factory Pattern versus a simple Switch statement in a growing codebase?
  • QHow do you prevent memory leaks when implementing the Observer pattern in a Single Page Application (SPA)?
  • QExplain the Strategy Pattern and provide a code example for handling multiple payment methods (Stripe, PayPal, Crypto).

Frequently Asked Questions

Are design patterns still relevant in modern React/Vue development?

Absolutely. While frameworks handle some patterns for you (like the Observer pattern via State), understanding the Strategy pattern helps with complex prop-drilling scenarios, and the Factory pattern is excellent for dynamically rendering UI components from a CMS response.

Is the Singleton pattern an Anti-Pattern?

It is only an anti-pattern when used to hide global variables. If it's used to manage a shared hardware resource or a single network socket, it is a perfectly valid and necessary tool.

What is the difference between the Observer and Pub-Sub patterns?

In the Observer pattern, the Subject knows about its Observers (it maintains a list). In Pub-Sub, there is a third 'Broker' component (like an Event Bus) that sits between them, so the publisher and subscriber never know each other exist.

How do I implement a Private Constructor in JavaScript for a Singleton?

JavaScript doesn't have true private constructors yet. The best practice is to throw an error in the constructor if the instance already exists, or use a static flag to check if the instance is being created from within the class.

🔥
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousMemoisation in JavaScriptNext →Functional Programming in JS
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged