Patterns & Anti-patterns Playbook
Proven design patterns and common pitfalls to avoid. Use these to make better architectural decisions.
# agent_prompt
Copy this prompt and give it to your AI agent:
"When writing code, apply these pattern guidelines:
1. Use composition over inheritance
2. Prefer small, focused functions over large, complex ones
3. Extract shared logic into utility functions
4. Use dependency injection for testability
5. Apply the strategy pattern for varying algorithms
6. Avoid: God objects, deep inheritance, global state, callback hell"
# useful_patterns
Factory Pattern
Create objects without specifying their exact class. Great for creating different implementations based on conditions.
// When to use: Creating different object types based on input
function createNotification(type, message) {
const notifications = {
email: () => new EmailNotification(message),
sms: () => new SMSNotification(message),
push: () => new PushNotification(message),
};
return notifications[type]?.() ?? new DefaultNotification(message);
}
// Usage
const notification = createNotification('email', 'Hello!');
notification.send(); Use when: Object creation logic is complex or varies by type.
Strategy Pattern
Define a family of algorithms and make them interchangeable. Replace conditionals with polymorphism.
// When to use: Multiple algorithms for the same task
const pricingStrategies = {
standard: (price) => price,
premium: (price) => price * 1.5,
sale: (price) => price * 0.8,
bulk: (price, quantity) => price * (quantity > 10 ? 0.9 : 1),
};
function calculatePrice(basePrice, strategy, ...args) {
return pricingStrategies[strategy](basePrice, ...args);
}
// Usage
calculatePrice(100, 'premium'); // 150
calculatePrice(100, 'sale'); // 80
calculatePrice(100, 'bulk', 15); // 90 Observer / Event Emitter Pattern
Allow objects to subscribe to and react to events. Decouples event producers from consumers.
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
return () => this.off(event, callback); // Return unsubscribe
}
emit(event, data) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
}
// Usage
const events = new EventEmitter();
const unsubscribe = events.on('userCreated', user => {
sendWelcomeEmail(user);
});
events.emit('userCreated', { name: 'Alice' }); Composition over Inheritance
Build complex functionality by combining simple functions or objects instead of deep inheritance hierarchies.
❌ Inheritance
class Animal { }
class Dog extends Animal { }
class SwimmingDog extends Dog { }
class FlyingDog extends Dog { }
// What about a swimming flying dog? ✅ Composition
const canSwim = { swim() { ... } };
const canFly = { fly() { ... } };
const canBark = { bark() { ... } };
const dog = { ...canBark };
const superDog = {
...canBark, ...canSwim, ...canFly
}; # anti_patterns
God Object / God Class
A class that knows too much or does too much. Violates single responsibility.
❌ God Object
class Application {
handleLogin() { ... }
sendEmail() { ... }
processPayment() { ... }
generateReport() { ... }
validateForm() { ... }
uploadFile() { ... }
// 50 more methods...
} ✅ Separated concerns
class AuthService {
login() { ... }
logout() { ... }
}
class EmailService {
send() { ... }
}
class PaymentService {
process() { ... }
} Callback Hell / Pyramid of Doom
Deeply nested callbacks that make code unreadable and hard to maintain.
❌ Nested callbacks
getUser(id, (user) => {
getOrders(user.id, (orders) => {
getProducts(orders[0].id, (products) => {
getReviews(products[0].id, (reviews) => {
// Now what?
});
});
});
}); ✅ Async/await
async function getData(id) {
const user = await getUser(id);
const orders = await getOrders(user.id);
const products = await getProducts(orders[0].id);
const reviews = await getReviews(products[0].id);
return { user, orders, products, reviews };
} Magic Numbers & Strings
Unexplained literal values scattered throughout code. Makes maintenance difficult.
❌ Magic values
if (user.role === 2) { ... }
if (retries > 3) { ... }
const tax = price * 0.0825;
setTimeout(fn, 86400000); ✅ Named constants
const ROLE_ADMIN = 2;
const MAX_RETRIES = 3;
const TAX_RATE = 0.0825;
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
if (user.role === ROLE_ADMIN) { ... }
setTimeout(fn, ONE_DAY_MS); Premature Optimization
Optimizing code before you know where the bottlenecks are. Results in complex code that may not even improve performance.
"Premature optimization is the root of all evil" — Donald Knuth
- • First: Make it work correctly
- • Then: Measure actual performance
- • Finally: Optimize the real bottlenecks
Copy-Paste Programming
Duplicating code instead of abstracting. Creates maintenance nightmares when bugs need fixing in multiple places.
Rule of Three: When you copy code a third time, extract it.
- • Extract to a function if logic is duplicated
- • Create a component if UI is duplicated
- • Use configuration if only values differ
- • Consider generics if types are duplicated
Stringly Typed
Using strings where enums, constants, or types would be better. No type safety, prone to typos.
❌ Stringly typed
user.status = "actve"; // Typo!
if (status === "pending") { ... }
sendEvent("user_singed_up"); // Typo! ✅ Type-safe
const Status = {
ACTIVE: 'active',
PENDING: 'pending',
} as const;
user.status = Status.ACTIVE;
if (status === Status.PENDING) { ... }