Creational Patterns
5 patterns🎯 PROBLEM IT SOLVES
When you need exactly one object in your entire app — like app settings or the logged-in user. Creating multiple instances would cause conflicts.
💡 WHEN TO USE
- App settings (theme, language)
- Currently logged-in user
- Logger / error tracking
- Database connection pool
// We made two separate user config objects const user1 = { name: "Ali", theme: "dark" }; const user2 = { name: "Ali", theme: "light" }; // Which one is the real config? 🤔
const CurrentUser = (function() { let instance; return { getInstance() { if (!instance) { instance = { name: "Ali", theme: "dark" }; } return instance; } }; })(); const a = CurrentUser.getInstance(); const b = CurrentUser.getInstance(); a.theme = "light"; console.log(b.theme); // "light" ← same object ✅
🎯 PROBLEM IT SOLVES
Instead of writing 'new' everywhere, you tell the factory 'give me a user of this type' and it handles everything. Your code never needs to know the details.
💡 WHEN TO USE
- Different user types (Admin / Guest / Member)
- Different payment methods (Card / Wallet / Cash)
- Different notifications (Email / SMS)
// Want to add a new type? Must edit everywhere 😩 function createUser(type) { if (type === "admin") return new Admin(); if (type === "guest") return new Guest(); // Must come here every time you add a new type }
class AdminUser { greet() { return "Hi Admin 👑"; } } class GuestUser { greet() { return "Hi Guest 👤"; } } class MemberUser { greet() { return "Hi Member ⭐"; } } function UserFactory(type) { const users = { admin: AdminUser, guest: GuestUser, member: MemberUser }; return new users[type](); } const user = UserFactory("admin"); user.greet(); // "Hi Admin 👑" ✅
🎯 PROBLEM IT SOLVES
When creating a group of objects that must match each other — like a user's profile page where the button, card, and badge must all share the same theme.
💡 WHEN TO USE
- Dark / Light theme components
- Arabic / English UI sets
- Mobile / Desktop layouts
// Dark button with Light card = looks wrong 😬 const btn = new DarkButton(); const card = new LightCard(); // ❌ mismatch!
class DarkTheme { button() { return "🖤 Dark Button"; } card() { return "🖤 Dark Card"; } } class LightTheme { button() { return "🤍 Light Button"; } card() { return "🤍 Light Card"; } } function renderProfile(theme) { console.log(theme.button()); // always consistent ✅ console.log(theme.card()); } renderProfile(new DarkTheme()); // everything is dark ✅
🎯 PROBLEM IT SOLVES
When creating an object with many optional properties — like a user profile. Instead of sending 10 parameters to the constructor, you build it step by step.
💡 WHEN TO USE
- Build User Profile step by step
- Configure search filters
- Build an email message
// What does each parameter mean? 😵 new User("Ali", "ali@email.com", 25, true, false, "Cairo");
class UserBuilder { constructor(name) { this.user = { name }; } setEmail(e) { this.user.email = e; return this; } setAge(a) { this.user.age = a; return this; } setCity(c) { this.user.city = c; return this; } setAdmin() { this.user.isAdmin=true; return this; } build() { return this.user; } } const user = new UserBuilder("Ali") .setEmail("ali@email.com") .setAge(25) .setCity("Cairo") .setAdmin() .build(); // Crystal clear! ✅
🎯 PROBLEM IT SOLVES
When you have an existing user and want a copy with minor changes — instead of rebuilding from scratch, just clone it and change what's different.
💡 WHEN TO USE
- Copy user settings for a new user
- Duplicate profile
- Template accounts
// Ali and Ahmed have almost the same settings 😩 const ali = { theme:"dark", lang:"ar", role:"user" }; const ahmed = { theme:"dark", lang:"ar", role:"admin" };
const defaultUser = { theme: "dark", lang: "ar", role: "user", clone() { return { ...this }; } }; const ali = defaultUser.clone(); const ahmed = defaultUser.clone(); ahmed.role = "admin"; // Change only what you need ✅ // Or with Object.create const omar = Object.create(defaultUser); omar.name = "Omar"; // inherits everything from defaultUser ✅
Structural Patterns
7 patterns🎯 PROBLEM IT SOLVES
You have an old API that returns user data in one shape, and your new code expects a different shape. Adapter translates between them without changing either side.
💡 WHEN TO USE
- Connect old API to new code
- Transform response shape
- Use a library without modifying it
// Old API returns this const oldUser = { full_name: "Ali Ahmed", usr_age: 25 }; // Your new code expects this // { name: "Ali Ahmed", age: 25 } ← won't work! 😩
function UserAdapter(oldUser) { return { name: oldUser.full_name, age: oldUser.usr_age }; } const oldUser = { full_name: "Ali Ahmed", usr_age: 25 }; const user = UserAdapter(oldUser); console.log(user.name); // "Ali Ahmed" ✅ console.log(user.age); // 25 ✅
🎯 PROBLEM IT SOLVES
You have User Notifications — can be sent via Email or SMS, and can be Alert or Reminder types. Instead of one class per combination, separate them.
💡 WHEN TO USE
- Notifications with different types and channels
- Export files in different formats
- Avoid N×M class explosion
// EmailAlert, SMSAlert, EmailReminder, SMSReminder... 😩 class EmailAlert {} class SMSAlert {} class EmailReminder {} // Grows endlessly!
// Channel (implementation) const Email = { send(msg) { console.log("📧 Email: "+msg); } }; const SMS = { send(msg) { console.log("📱 SMS: "+msg); } }; // Type (abstraction) class Alert { constructor(sender) { this.sender = sender; } notify(user) { this.sender.send("🚨 Alert for: "+user); } } class Reminder { constructor(sender) { this.sender = sender; } notify(user) { this.sender.send("⏰ Reminder for: "+user); } } new Alert(Email).notify("Ali"); // 📧 Email Alert ✅ new Reminder(SMS).notify("Ali"); // 📱 SMS Reminder ✅
🎯 PROBLEM IT SOLVES
A user has individual permissions and permission groups. When checking access, you should handle both the same way without distinguishing.
💡 WHEN TO USE
- Permission system (user / group)
- Menu with submenus
- Folder containing files and subfolders
// Individual permission class Permission { constructor(name) { this.name = name; } show() { console.log(" 🔑 " + this.name); } } // Group of permissions class PermissionGroup { constructor(name) { this.name=name; this.items=[]; } add(p) { this.items.push(p); return this; } show() { console.log("📁 " + this.name); this.items.forEach(p => p.show()); } } const admin = new PermissionGroup("Admin") .add(new Permission("read")) .add(new Permission("write")) .add(new Permission("delete")); admin.show(); // displays all permissions ✅
🎯 PROBLEM IT SOLVES
Plain User → add Premium → add Verified. Instead of a new class for each combination, wrap the object and stack features on top.
💡 WHEN TO USE
- Add badges to user (Verified, Premium)
- Express middleware pipeline
- Add features without modifying the class
// PremiumUser, VerifiedUser, PremiumVerifiedUser... 😩 class PremiumVerifiedUser extends User { }
class User { constructor(name) { this.name = name; } getInfo() { return this.name; } } class PremiumDecorator { constructor(user) { this.user = user; } getInfo() { return this.user.getInfo() + " ⭐"; } } class VerifiedDecorator { constructor(user) { this.user = user; } getInfo() { return this.user.getInfo() + " ✅"; } } let user = new User("Ali"); user = new PremiumDecorator(user); user = new VerifiedDecorator(user); user.getInfo(); // "Ali ⭐ ✅" ✅
🎯 PROBLEM IT SOLVES
When a user registers, many things happen behind the scenes: save to DB, send email, create session. Facade puts all that behind one simple call.
💡 WHEN TO USE
- User registration / login flow
- Checkout process
- Simplify complex APIs
// All this inside the component?! 😩 db.saveUser(user); email.sendWelcome(user); session.create(user); logger.log("registered");
class AuthFacade { register(userData) { db.saveUser(userData); email.sendWelcome(userData.email); session.create(userData.id); logger.log("User registered: " + userData.name); return "✅ Welcome!"; } } const auth = new AuthFacade(); auth.register({ name: "Ali", email: "ali@x.com" }); // Simple and clean ✅
🎯 PROBLEM IT SOLVES
You have a million users and they all share the same country data. Instead of duplicating that data a million times, share one object between them all.
💡 WHEN TO USE
- Country/City data shared between users
- Shared avatars or icons
- When memory matters
// Every user stores the same country data 😩 const users = [ { name:"Ali", country: { name:"Egypt", code:"EG", flag:"🇪🇬" } }, { name:"Ahmed", country: { name:"Egypt", code:"EG", flag:"🇪🇬" } }, // Same object duplicated a million times 😩 ];
const CountryPool = {}; function getCountry(code) { if (!CountryPool[code]) { CountryPool[code] = { name: "Egypt", code, flag: "🇪🇬" }; } return CountryPool[code]; // same reference ✅ } const ali = { name: "Ali", country: getCountry("EG") }; const ahmed = { name: "Ahmed", country: getCountry("EG") }; console.log(ali.country === ahmed.country); // true ✅ exact same object
🎯 PROBLEM IT SOLVES
Before reaching user data, the Proxy checks: do you have permission? It also caches results so you don't hit the DB every time.
💡 WHEN TO USE
- Access control on user data
- Caching user profiles
- Logging requests
// Every getUser() call makes a DB query 😩 function getUser(id) { return db.query(`SELECT * FROM users WHERE id=${id}`); }
const UserProxy = { cache: {}, getUser(id, requester) { // 1. Access Control if (!requester.isAdmin && requester.id !== id) { return "❌ Access Denied"; } // 2. Cache if (this.cache[id]) { console.log("📦 From cache"); return this.cache[id]; } this.cache[id] = db.query(id); return this.cache[id]; } }; // Same interface, but smarter ✅
Behavioral Patterns
11 patterns🎯 PROBLEM IT SOLVES
When the user profile changes, you want the dashboard, notifications, and header to all update automatically — without each part knowing about the others.
💡 WHEN TO USE
- User profile update → notify all components
- Chat messages (real-time)
- DOM events (addEventListener)
- State management (Redux)
// Must know and call every component manually 😩 function updateProfile(user) { dashboard.refresh(user); header.update(user); notifications.push(user); }
class UserStore { constructor() { this.listeners = []; } subscribe(fn) { this.listeners.push(fn); } updateProfile(data) { // notifies all subscribers 🔔 this.listeners.forEach(fn => fn(data)); } } const store = new UserStore(); store.subscribe(u => console.log("Header: " + u.name)); store.subscribe(u => console.log("Dashboard: " + u.name)); store.updateProfile({ name: "Ali Updated" }); // both notified ✅
🎯 PROBLEM IT SOLVES
A user wants to log in using Email, Google, or Facebook. Instead of a giant if/else, each method becomes an independent strategy.
💡 WHEN TO USE
- Login strategies (Email/Google/Facebook)
- Different payment methods
- Sorting algorithms
function login(method, data) { if (method === "email") { /* ... */ } else if (method === "google") { /* ... */ } else if (method === "facebook") { /* ... */ } // Add Apple login? Must edit this function 😩 }
const EmailLogin = { auth(d) { return "📧 Email: " +d.email; } }; const GoogleLogin = { auth(d) { return "🔵 Google: " +d.token; } }; const FacebookLogin = { auth(d) { return "🔷 Facebook: " +d.token; } }; class AuthService { constructor(strategy) { this.strategy = strategy; } setStrategy(s) { this.strategy = s; } login(data) { return this.strategy.auth(data); } } const auth = new AuthService(EmailLogin); auth.login({ email: "ali@x.com" }); // 📧 Email ✅ auth.setStrategy(GoogleLogin); auth.login({ token: "abc" }); // 🔵 Google ✅
🎯 PROBLEM IT SOLVES
User changed their name — you want undo to restore the old name. Command saves every action as an object with execute() and undo().
💡 WHEN TO USE
- Undo/Redo for profile editing
- Save edit history
- Queue of actions
class UpdateNameCommand { constructor(user, newName) { this.user = user; this.newName = newName; this.oldName = user.name; // save old value for undo } execute() { this.user.name = this.newName; } undo() { this.user.name = this.oldName; } } const user = { name: "Ali" }; const history = []; const cmd = new UpdateNameCommand(user, "Ahmed"); cmd.execute(); history.push(cmd); console.log(user.name); // "Ahmed" history.pop().undo(); console.log(user.name); // "Ali" ← restored ✅
🎯 PROBLEM IT SOLVES
You have a Users Collection that might be an array, linked list, or paginated API. Iterator gives you a consistent way to loop without knowing the internal structure.
💡 WHEN TO USE
- Paginated users list
- Custom collection traversal
- JS for...of on custom objects
class UserCollection { constructor() { this.users = []; } add(u) { this.users.push(u); } [Symbol.iterator]() { let i = 0; const users = this.users; return { next() { return i < users.length ? { value: users[i++], done: false } : { done: true }; } }; } } const list = new UserCollection(); list.add({ name: "Ali" }); list.add({ name: "Ahmed" }); for (const user of list) { console.log(user.name); // Ali, Ahmed ✅ }
🎯 PROBLEM IT SOLVES
In a chat, every user needs to talk to every other user — chaos! Instead, everything goes through the ChatRoom which distributes messages.
💡 WHEN TO USE
- Chat room / group messaging
- Event bus between components
- Air traffic control
class ChatRoom { constructor() { this.users = []; } join(user) { this.users.push(user); user.room = this; } send(msg, sender) { this.users .filter(u => u !== sender) .forEach(u => u.receive(`${sender.name}: ${msg}`)); } } class User { constructor(name) { this.name = name; } send(msg) { this.room.send(msg, this); } receive(msg) { console.log("📩 " + msg); } } const room = new ChatRoom(); const ali = new User("Ali"); const ahmed = new User("Ahmed"); room.join(ali); room.join(ahmed); ali.send("Hello everyone!"); // 📩 Ali: Hello everyone! ✅
🎯 PROBLEM IT SOLVES
A user edits their profile — you want them to be able to restore any previous version. Memento saves snapshots of the state.
💡 WHEN TO USE
- Profile edit history
- Form autosave / draft
- Game save states
- Ctrl+Z undo
class UserProfile { constructor(name, bio) { this.name=name; this.bio=bio; } save() { return { name:this.name, bio:this.bio }; } restore(s){ this.name=s.name; this.bio=s.bio; } } const profile = new UserProfile("Ali", "Developer"); const snapshots = []; snapshots.push(profile.save()); // save before editing profile.name = "Ali Ahmed"; profile.bio = "Senior Developer"; console.log(profile.name); // "Ali Ahmed" profile.restore(snapshots.pop()); console.log(profile.name); // "Ali" ← restored ✅
🎯 PROBLEM IT SOLVES
A user account has states: Active, Banned, Pending. When they take an action, the behavior changes based on the current state — no if/else needed.
💡 WHEN TO USE
- User account status
- Order status (pending/shipped/delivered)
- Traffic light
- Media player (play/pause/stop)
function doAction(user) { if (user.status === "active") { /* ... */ } else if (user.status === "banned") { /* ... */ } else if (user.status === "pending") { /* ... */ } }
class ActiveState { login() { return "✅ Welcome back!"; } post() { return "📝 Post published!"; } } class BannedState { login() { return "❌ Account banned!"; } post() { return "❌ Cannot post!"; } } class PendingState { login() { return "⏳ Verify email first"; } post() { return "⏳ Not activated yet"; } } class UserAccount { constructor() { this.state = new PendingState(); } setState(s) { this.state = s; } login() { return this.state.login(); } post() { return this.state.post(); } } const acc = new UserAccount(); acc.login(); // ⏳ Verify email acc.setState(new ActiveState()); acc.login(); // ✅ Welcome back! ✅
🎯 PROBLEM IT SOLVES
All user types log in with the same steps (validate → authenticate → log) but each type implements each step its own way.
💡 WHEN TO USE
- Login flow for different user types
- Export report (same steps, different formats)
- Build pipelines
class LoginTemplate { login(data) { // the template — never changes this.validate(data); this.authenticate(data); this.logSuccess(data); } logSuccess(d){ console.log("✅ Logged: "+d.user); } // default validate() { throw "implement validate"; } authenticate(){ throw "implement authenticate"; } } class EmailLogin extends LoginTemplate { validate(d) { console.log("📧 Validate email: "+d.email); } authenticate(d) { console.log("🔑 Check password"); } } class GoogleLogin extends LoginTemplate { validate(d) { console.log("🔵 Validate token: "+d.token); } authenticate(d) { console.log("🔵 Verify with Google"); } } new EmailLogin().login({ email:"ali@x.com", user:"Ali" }); // ✅
🎯 PROBLEM IT SOLVES
When a user sends a request, it passes through checkpoints: Are they logged in? Do they have permission? Is the data valid? Each checkpoint decides to handle or pass along.
💡 WHEN TO USE
- Auth → Permission → Validation
- Express.js middleware
- Support ticket escalation
class Middleware { setNext(m) { this.next = m; return m; } handle(req) { return this.next?.handle(req); } } class AuthCheck extends Middleware { handle(req) { if (!req.user) return "❌ Login first!"; return super.handle(req); } } class AdminCheck extends Middleware { handle(req) { if (!req.user.isAdmin) return "❌ Admins only!"; return super.handle(req); } } class Handler extends Middleware { handle() { return "✅ Welcome Admin!"; } } const chain = new AuthCheck(); chain.setNext(new AdminCheck()).setNext(new Handler()); chain.handle({ user: { name:"Ali", isAdmin:true } }); // ✅
🎯 PROBLEM IT SOLVES
You have different user types (FreeUser, PremiumUser) and want to perform operations on them (export, report, discount) without modifying the user classes.
💡 WHEN TO USE
- Reports across different user types
- Export in different formats
- Add operations without changing classes
class FreeUser { accept(v){ v.visitFree(this); }; name="Ali"; } class PremiumUser { accept(v){ v.visitPremium(this); }; name="Ahmed"; } // New visitor without touching the classes class DiscountVisitor { visitFree(u) { console.log(`${u.name}: 10% discount 🎁`); } visitPremium(u) { console.log(`${u.name}: 30% discount 🎁`); } } class ReportVisitor { visitFree(u) { console.log(`${u.name}: Free plan 📊`); } visitPremium(u) { console.log(`${u.name}: Premium plan 📊`); } } const users = [new FreeUser(), new PremiumUser()]; users.forEach(u => u.accept(new DiscountVisitor())); // ✅
🎯 PROBLEM IT SOLVES
User types a search query like "name=Ali AND age>20" — you need to understand and translate that text into a real filter.
💡 WHEN TO USE
- User search / filter queries
- SQL parser
- Template engine
- Math expression calculator
// Simple filter system for users class NameFilter { constructor(name) { this.name = name; } interpret(users) { return users.filter(u => u.name === this.name); } } class AgeFilter { constructor(min) { this.min = min; } interpret(users) { return users.filter(u => u.age > this.min); } } class AndFilter { constructor(a, b) { this.a=a; this.b=b; } interpret(users) { return this.b.interpret(this.a.interpret(users)); } } const users = [ { name:"Ali", age:25 }, { name:"Ali", age:17 }, { name:"Ahmed", age:30 } ]; // "name=Ali AND age>20" const query = new AndFilter(new NameFilter("Ali"), new AgeFilter(20)); query.interpret(users); // [{ name:"Ali", age:25 }] ✅
JavaScript Patterns
4 patterns🎯 PROBLEM IT SOLVES
You want to create many users all with the same shape — same properties and same methods. Constructor gives you a blueprint to stamp out instances.
💡 WHEN TO USE
- Create many users from the same template
- OOP in JavaScript
- Add methods on the prototype
// Every user requires writing the same methods again 😩 const ali = { name:"Ali", greet() { return "Hi, I'm Ali"; } }; const ahmed = { name:"Ahmed", greet() { return "Hi, I'm Ahmed"; } };
// function constructor (old way) function User(name, age) { this.name = name; this.age = age; } // method on prototype (shared by all instances) User.prototype.greet = function() { return `Hi, I'm ${this.name}!`; }; // class syntax (modern way — same idea) class User { constructor(name, age) { this.name=name; this.age=age; } greet() { return `Hi, I'm ${this.name}!`; } } const ali = new User("Ali", 25); const ahmed = new User("Ahmed", 30); ali.greet(); // "Hi, I'm Ali!" ✅ ahmed.greet(); // "Hi, I'm Ahmed!" ✅
🎯 PROBLEM IT SOLVES
Sensitive user data (like passwords) shouldn't be visible or editable from outside. Module hides private data and only exposes what's needed.
💡 WHEN TO USE
- Hide sensitive data
- Encapsulation in JS
- Organize code into units
let userName = "Ali"; let userPassword = "1234"; // Anyone can read and modify it! 😩 userName = "Hacker"; // so easy to tamper with
const UserModule = (function() { // ❌ private — not accessible from outside let _password = "1234"; let _name = "Ali"; // ✅ public — this is what we expose return { getName() { return _name; }, setName(n) { if (n.length > 2) _name = n; // validation }, checkPassword(p) { return p === _password; } }; })(); UserModule.getName(); // "Ali" ✅ UserModule._password; // undefined ✅ (protected by closure!) UserModule.checkPassword("1234"); // true ✅
🎯 PROBLEM IT SOLVES
An improvement on Module Pattern — instead of writing some functions inside the return and others outside, write everything inside and only "reveal" what you want.
💡 WHEN TO USE
- When you want all code organized in one place
- Easier to read and maintain
- Clearly define what is public and what is private
const UserModule = (function() { let name = "Ali"; function validate(n) { return n.length > 2; } // private here return { setName(n) { // public here — two different places 😕 if (validate(n)) name = n; } }; })();
const UserProfile = (function() { let _name = "Ali"; let _email = "ali@x.com"; function _validate(n) { return n.length > 2; } function getName() { return _name; } function getEmail() { return _email; } function setName(n) { if (_validate(n)) _name = n; } // ✅ only here you define what gets revealed return { getName, getEmail, setName }; })(); UserProfile.getName(); // "Ali" ✅ UserProfile.setName("Ahmed"); UserProfile.getName(); // "Ahmed" ✅ UserProfile._validate; // undefined ✅ (private)
🎯 PROBLEM IT SOLVES
User and Admin share behaviors (canLogin, canPost) but you don't want complex inheritance. Mixin adds behaviors to any class without using extends.
💡 WHEN TO USE
- Add behaviors to different classes
- When JS single inheritance limits you
- Reusable behaviors without inheritance
class User { canLogin() {/* same code */} canPost(){/* same code */} } class Admin { canLogin() {/* same code */} canPost(){/* same code */} } // Same code copy-pasted everywhere 😩
// Mixin — reusable behaviors const UserMixin = { canLogin() { return `✅ ${this.name} logged in`; }, canPost() { return `📝 ${this.name} posted`; }, getProfile(){ return `👤 ${this.name} - ${this.role}`; } }; class NormalUser { constructor(name){ this.name=name; this.role="user"; } } class AdminUser { constructor(name){ this.name=name; this.role="admin"; } } // Add the Mixin to both classes Object.assign(NormalUser.prototype, UserMixin); Object.assign(AdminUser.prototype, UserMixin); const ali = new NormalUser("Ali"); const admin = new AdminUser("Ahmed"); ali.canLogin(); // Ali logged in ✅ admin.getProfile(); // 👤 Ahmed - admin ✅