JavaScript Design Patterns Explained — Singleton, Observer, Factory and More
- 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.
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 - 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());
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 - 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);
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 - 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');
| Pattern | Category | Primary Use Case |
|---|---|---|
| Singleton | Creational | Shared state/resource (e.g., Database Pool, Config) |
| Factory | Creational | Creating objects without exposing creation logic |
| Observer | Behavioral | Event-driven updates (e.g., UI updates, WebSockets) |
| Module | Structural | Encapsulation and private/public scope management |
| Strategy | Behavioral | Swapping 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
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.
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.