FroquizFroquiz
HomeQuizzesSenior ChallengeGet CertifiedBlogAbout
Sign InStart Quiz
Sign InStart Quiz
Froquiz

The most comprehensive quiz platform for software engineers. Test yourself with 10000+ questions and advance your career.

LinkedIn

Platform

  • Start Quizzes
  • Topics
  • Blog
  • My Profile
  • Sign In

About

  • About Us
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Froquiz. All rights reserved.Built with passion for technology
Blog & Articles

JavaScript Design Patterns: Singleton, Observer, Factory, Module and More

Learn the most important JavaScript design patterns with practical examples. Covers Singleton, Observer, Factory, Module, Decorator, Strategy, and when to apply each.

Yusuf SeyitoğluMarch 11, 20260 views11 min read

JavaScript Design Patterns: Singleton, Observer, Factory, Module and More

Design patterns are reusable solutions to commonly occurring problems in software design. They are not templates you copy and paste β€” they are concepts that, once understood, you will recognize and apply naturally. In JavaScript interviews, knowing patterns demonstrates that you write intentional, maintainable code.

Why Design Patterns Matter

Without patterns, developers solve the same problems repeatedly in inconsistent ways. Patterns give teams a shared vocabulary β€” saying "let's use an Observer here" is faster than explaining the concept from scratch. They also represent decades of battle-tested wisdom about what works.

Creational Patterns

Creational patterns deal with object creation.

Singleton

Ensures a class has only one instance and provides a global access point to it.

javascript
class DatabaseConnection { constructor(url) { if (DatabaseConnection.instance) { return DatabaseConnection.instance; } this.url = url; this.connected = false; DatabaseConnection.instance = this; } connect() { if (!this.connected) { console.log(`Connecting to ${this.url}`); this.connected = true; } return this; } query(sql) { if (!this.connected) throw new Error("Not connected"); return `Result of: ${sql}`; } } const db1 = new DatabaseConnection("postgres://localhost/mydb"); const db2 = new DatabaseConnection("postgres://other/db"); console.log(db1 === db2); // true -- same instance console.log(db2.url); // "postgres://localhost/mydb"

Modern JavaScript often uses module-level singletons instead:

javascript
// db.js -- the module is loaded once; this is the singleton import { Pool } from "pg"; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); export default pool; // Any file that imports pool gets the same instance import pool from "./db.js";

Use when: You need exactly one instance of something β€” a connection pool, a configuration object, a logger.

Avoid when: It introduces global state that makes testing hard. Consider dependency injection instead.

Factory

Creates objects without specifying their exact class, delegating the instantiation logic to a factory function or method.

javascript
class Button { constructor(text, style) { this.text = text; this.style = style; } render() { return `<button class="${this.style}">${this.text}</button>`; } } class IconButton extends Button { constructor(text, icon) { super(text, "icon-btn"); this.icon = icon; } render() { return `<button class="${this.style}"><span>${this.icon}</span> ${this.text}</button>`; } } class LinkButton extends Button { constructor(text, href) { super(text, "link-btn"); this.href = href; } render() { return `<a href="${this.href}" class="${this.style}">${this.text}</a>`; } } // Factory function function createButton(type, options) { switch (type) { case "icon": return new IconButton(options.text, options.icon); case "link": return new LinkButton(options.text, options.href); default: return new Button(options.text, options.style ?? "btn"); } } const btn = createButton("icon", { text: "Save", icon: "πŸ’Ύ" }); const link = createButton("link", { text: "Learn more", href: "/docs" });

Use when: Object creation logic is complex, varies by type, or you want to hide implementation details from callers.

Builder

Constructs complex objects step by step, separating construction from representation.

javascript
class QueryBuilder { #table = ""; #conditions = []; #selectedCols = ["*"]; #limitVal = null; #orderByCol = null; from(table) { this.#table = table; return this; // enable chaining } select(...cols) { this.#selectedCols = cols; return this; } where(condition) { this.#conditions.push(condition); return this; } limit(n) { this.#limitVal = n; return this; } orderBy(col) { this.#orderByCol = col; return this; } build() { let query = `SELECT ${this.#selectedCols.join(", ")} FROM ${this.#table}`; if (this.#conditions.length) { query += ` WHERE ${this.#conditions.join(" AND ")}`; } if (this.#orderByCol) query += ` ORDER BY ${this.#orderByCol}`; if (this.#limitVal) query += ` LIMIT ${this.#limitVal}`; return query; } } const query = new QueryBuilder() .from("users") .select("id", "name", "email") .where("active = true") .where("age >= 18") .orderBy("name") .limit(20) .build(); // SELECT id, name, email FROM users WHERE active = true AND age >= 18 ORDER BY name LIMIT 20

Structural Patterns

Structural patterns deal with object composition.

Module Pattern

Encapsulates related code with private state. The foundation of JavaScript modularity before ES modules.

javascript
const CartModule = (function() { // Private state const items = []; let discount = 0; // Private function function calculateSubtotal() { return items.reduce((sum, item) => sum + item.price * item.qty, 0); } // Public API return { addItem(item) { const existing = items.find(i => i.id === item.id); if (existing) { existing.qty += item.qty; } else { items.push({ ...item }); } }, removeItem(id) { const index = items.findIndex(i => i.id === id); if (index !== -1) items.splice(index, 1); }, setDiscount(pct) { discount = pct; }, getTotal() { return calculateSubtotal() * (1 - discount / 100); }, getItems() { return [...items]; // return copy, not reference }, }; })(); CartModule.addItem({ id: 1, name: "Laptop", price: 999, qty: 1 }); CartModule.setDiscount(10); console.log(CartModule.getTotal()); // 899.1 console.log(CartModule.items); // undefined -- private

Decorator

Adds behavior to objects dynamically without modifying their class.

javascript
class Coffee { cost() { return 2; } description() { return "Basic coffee"; } } class MilkDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 0.5; } description() { return this.coffee.description() + ", milk"; } } class SyrupDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 0.75; } description() { return this.coffee.description() + ", syrup"; } } class WhipDecorator { constructor(coffee) { this.coffee = coffee; } cost() { return this.coffee.cost() + 0.6; } description() { return this.coffee.description() + ", whip"; } } let order = new Coffee(); order = new MilkDecorator(order); order = new SyrupDecorator(order); order = new WhipDecorator(order); console.log(order.description()); // Basic coffee, milk, syrup, whip console.log(order.cost()); // 3.85

Behavioral Patterns

Behavioral patterns deal with communication between objects.

Observer (Publish/Subscribe)

Defines a one-to-many dependency β€” when one object changes state, all dependents are notified.

javascript
class EventEmitter { #listeners = new Map(); on(event, listener) { if (!this.#listeners.has(event)) { this.#listeners.set(event, new Set()); } this.#listeners.get(event).add(listener); return () => this.off(event, listener); // return unsubscribe fn } off(event, listener) { this.#listeners.get(event)?.delete(listener); } emit(event, ...args) { this.#listeners.get(event)?.forEach(listener => listener(...args)); } once(event, listener) { const wrapper = (...args) => { listener(...args); this.off(event, wrapper); }; return this.on(event, wrapper); } } const store = new EventEmitter(); const unsubscribe = store.on("userLoggedIn", (user) => { console.log(`Welcome, ${user.name}!`); }); store.on("userLoggedIn", (user) => { analytics.track("login", { userId: user.id }); }); store.emit("userLoggedIn", { id: 1, name: "Alice" }); unsubscribe(); // clean up first listener

Strategy

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

javascript
const sortStrategies = { bubble(arr) { const a = [...arr]; for (let i = 0; i < a.length; i++) for (let j = 0; j < a.length - i - 1; j++) if (a[j] > a[j + 1]) [a[j], a[j + 1]] = [a[j + 1], a[j]]; return a; }, quick(arr) { if (arr.length <= 1) return arr; const pivot = arr[arr.length - 1]; const left = arr.slice(0, -1).filter(x => x <= pivot); const right = arr.slice(0, -1).filter(x => x > pivot); return [...sortStrategies.quick(left), pivot, ...sortStrategies.quick(right)]; }, native(arr) { return [...arr].sort((a, b) => a - b); }, }; class Sorter { constructor(strategy = "native") { this.strategy = sortStrategies[strategy]; } setStrategy(strategy) { this.strategy = sortStrategies[strategy]; } sort(arr) { return this.strategy(arr); } } const sorter = new Sorter("quick"); sorter.sort([5, 3, 8, 1, 9, 2]); // [1, 2, 3, 5, 8, 9] sorter.setStrategy("native"); sorter.sort([5, 3, 8, 1, 9, 2]); // same result, different algorithm

Common Interview Questions

Q: What is the difference between the Factory and Builder patterns?

Factory creates an object in one step, hiding the creation logic. Builder constructs a complex object step by step with a fluent interface β€” useful when there are many optional configurations. Use Factory when creation is simple but varies by type; use Builder when constructing complex objects with many parts.

Q: What problem does the Observer pattern solve?

It decouples the subject (producer) from its observers (consumers). The subject does not need to know who is observing or what they do with the data. Observers can subscribe and unsubscribe at runtime. This is the foundation of event systems, reactive programming, and pub/sub messaging.

Q: Is Singleton an anti-pattern?

It can be. Singletons introduce global mutable state that makes code harder to test (you cannot easily swap the singleton for a mock) and creates hidden dependencies. Module-level exports are a cleaner alternative in JavaScript. Use Singletons deliberately and sparingly.

Practice JavaScript on Froquiz

Design pattern questions appear in mid-level to senior JavaScript interviews. Test your JavaScript knowledge on Froquiz across all difficulty levels.

Summary

  • Singleton β€” one instance only; use module exports in modern JavaScript
  • Factory β€” create objects by type without exposing class details
  • Builder β€” construct complex objects step by step with chaining
  • Module β€” private state with a public API via IIFE or ES modules
  • Decorator β€” add behavior dynamically by wrapping objects
  • Observer β€” one-to-many notification without tight coupling
  • Strategy β€” swap algorithms at runtime without changing callers

About Author

Yusuf Seyitoğlu

Author β†’

Other Posts

  • CSS Advanced Techniques: Custom Properties, Container Queries, Grid Masonry and Modern LayoutsMar 12
  • Java Collections Deep Dive: ArrayList, HashMap, TreeMap, LinkedHashMap and When to Use EachMar 12
  • GraphQL Schema Design: Types, Resolvers, Mutations and Best PracticesMar 12
All Blogs