Design patterns aren't academic theory — they're solutions to problems we face daily at Google. Here are the three most practical ones.
Factory Pattern
Creates objects without specifying the exact class. Perfect for creating different UI components based on configuration.
function createNotification(type, message) {
const base = { message, timestamp: Date.now(), read: false };
switch (type) {
case "error": return { ...base, icon: "❌", priority: "high", color: "red" };
case "warning": return { ...base, icon: "⚠️", priority: "medium", color: "yellow" };
case "success": return { ...base, icon: "✅", priority: "low", color: "green" };
default: return { ...base, icon: "ℹ️", priority: "low", color: "blue" };
}
}Singleton Pattern
Ensures a class has only one instance. In JavaScript, modules are already singletons — but sometimes you need explicit control.
class Database {
static #instance = null;
#connection = null;
static getInstance() {
if (!Database.#instance) {
Database.#instance = new Database();
}
return Database.#instance;
}
connect(url) {
if (!this.#connection) {
this.#connection = createConnection(url);
}
return this.#connection;
}
}Observer Pattern
The foundation of event systems, React state, and pub/sub architectures.
class EventEmitter {
#listeners = new Map();
on(event, callback) {
if (!this.#listeners.has(event)) this.#listeners.set(event, new Set());
this.#listeners.get(event).add(callback);
return () => this.#listeners.get(event).delete(callback); // unsubscribe
}
emit(event, ...args) {
this.#listeners.get(event)?.forEach(cb => cb(...args));
}
}
const bus = new EventEmitter();
const unsub = bus.on("userLoggedIn", (user) => console.log(user));
bus.emit("userLoggedIn", { name: "Rahul" });
unsub(); // cleanupWhen to Use What
- Factory: When object creation logic is complex or varies by type
- Singleton: For shared resources (DB connections, loggers, config)
- Observer: For decoupled communication between components