Anti-Patterns & Code Smells
The recurring traps that quietly rot a codebase — God objects, service locators, magic values, premature optimization — and the fundamentals that cure each one.
Anti-patterns are common approaches that seem helpful but reliably make software worse: harder to change, test, and reason about. Code smells are the surface symptoms that warn you one is forming. Learning to recognize them — and knowing that most are cured by the same fundamentals (single responsibility, explicit dependencies, good naming, measuring before optimizing) — is what keeps a codebase healthy over years.
An anti-pattern is a commonly-used 'solution' that looks reasonable but reliably leads to negative consequences — harder maintenance, more bugs, tighter coupling. Unlike a simple mistake, it's a recurring trap that feels right in the moment, which is exactly why naming them matters.
God Object (one class doing everything), Spaghetti Code (no structure, tangled flow), and Copy-Paste duplication erode the structure of a codebase. They concentrate change risk and make every modification dangerous. The cure is almost always Single Responsibility and extracting cohesive units.
Service Locator and Singleton abuse hide dependencies behind global access, making code untestable and surprising. They mask what a class actually needs. The fix is explicit Dependency Injection — declare what you depend on so it's visible, required, and substitutable.
Premature Optimization (complexity before measurement), Golden Hammer (forcing a favorite tool everywhere), and over-engineering (patterns for problems you don't have) come from misapplied effort. The antidote is measuring before optimizing and choosing tools by the problem, not by habit.
Key Concepts
A class that knows/does too much, becoming a change magnet. Cured by splitting along responsibilities.
Pulling dependencies from a global registry, hiding them. Prefer explicit constructor injection.
Unexplained literals (3, 86400) scattered in code. Replace with named constants/enums.
Adding complexity for performance before profiling proves it's needed. Measure first.
Forcing one familiar tool/pattern onto every problem regardless of fit.
A surface symptom (long method, duplication, deep nesting) hinting at a deeper design problem.
1// 🚩 SMELL: God object + service locator + magic values2class OrderManager {3 process(order) {4 if (order.status === 3) { // magic number5 const repo = Locator.get("OrderRepo"); // hidden dependency6 const mailer = Locator.get("Mailer"); // hidden dependency7 // ...validation, tax, payment, inventory, email, pdf — all here8 }9 }10}1112// ✅ REFACTORED: SRP + dependency injection + named constants13class PlaceOrder {14 constructor(15 private repo: OrderRepository, // explicit, testable16 private payments: PaymentService,17 private mailer: Mailer,18 ) {}19 async execute(order: Order) {20 if (order.status !== OrderStatus.Pending) return; // named21 await this.payments.charge(order);22 await this.repo.save(order);23 await this.mailer.sendConfirmation(order);24 }25}
Spotting anti-patterns is a core skill in code review and a frequent interview topic ('what's wrong with this code?'). Naming them gives teams a shared language to discuss design problems, and recognizing them early prevents the slow accumulation of technical debt that eventually grinds a project to a halt.
Common Pitfalls
1The 4,000-Line Class Nobody Wants to Touch
A core OrderManager class has grown to 4,000 lines with 60 methods and 25 injected dependencies. Every feature edits it, merge conflicts are constant, and a small change keeps breaking unrelated functionality.
It's a textbook God Object: all responsibilities concentrated in one place. There are no seams to test in isolation, the blast radius of any change is the whole class, and developers fear modifying it.
Incrementally extract cohesive responsibilities into focused classes (validation, tax, payment, notification), each with a single reason to change, behind clear interfaces. Use the existing tests (or add characterization tests first) to refactor safely, shrinking the God Object until it's a thin coordinator or gone.
Takeaway: God Objects are paid down by applying Single Responsibility incrementally. Extract one cohesive concern at a time behind tests — you don't need a big-bang rewrite to escape a change-magnet class.
2Optimizing the Wrong 97%
A team spent a sprint hand-rolling a custom cache and bit-packing a data structure to speed up a feature. Performance didn't improve — because the real bottleneck was an N+1 query elsewhere they never measured.
Premature optimization: effort went into complex 'fast' code based on intuition, adding unreadable complexity to a path that wasn't hot, while the actual bottleneck went unexamined.
Profile first. A profiler immediately pointed at the N+1 query; fixing it with eager loading gave a 10× speedup in an afternoon. The premature micro-optimizations were reverted to restore readability.
Takeaway: Always measure before optimizing. Most time is spent in a tiny fraction of the code — profile to find it, optimize there, and keep everything else simple and readable.
Join the discussion
Comments
Loading comments...
Cookie preferences
We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.