What Are Common OOP Anti-Patterns?
A tour of common OOP anti-patterns — God Object, spaghetti code, anemic model, circular dependency — with causes, remedies and a Java example.
Expected Interview Answer
Common OOP anti-patterns include the God Object (one class doing everything), Spaghetti Code (tangled, untraceable control flow), the Anemic Domain Model (data-only classes with logic pulled out into separate service classes), Circular Dependencies (two or more classes depending on each other directly), and overuse of inheritance where composition would be more appropriate.
Each of these anti-patterns typically emerges gradually from reasonable-looking incremental decisions rather than a single bad design choice, which is what makes them hard to prevent without active vigilance. They share a common thread: they all increase coupling and reduce the ability to change one part of the system without understanding or breaking many others. Recognizing them early, during code review, is far cheaper than fixing them once dozens of other classes depend on the flawed structure. Standard remedies include applying the single responsibility principle, favoring composition over inheritance, introducing clear interfaces at module boundaries, and keeping behavior close to the data it operates on.
- Gives a shared vocabulary for flagging design problems in code review
- Helps teams recognize risky patterns before they compound
- Points directly to established remedies (SRP, composition, interfaces)
- Improves long-term maintainability and testability
AI Mentor Explanation
A cricket academy can go wrong in several distinct, recognizable ways: one overloaded administrator doing every job (a God Object), a fixture list so tangled nobody can trace which match affects which (spaghetti code), coaches who only record statistics with no say in training decisions (an anemic model, logic pulled elsewhere), or two clubs whose fixtures each depend on the other finishing first, so neither can ever schedule (a circular dependency). Naming each failure mode lets the academy’s board fix the actual structural problem instead of guessing. OOP anti-patterns work the same way: each has a distinct, recognizable shape, and naming it points straight at the remedy.
Step-by-Step Explanation
Step 1
Recognize the pattern
Compare the design against known shapes: God Object, spaghetti code, anemic model, circular dependency, inheritance misuse.
Step 2
Diagnose root cause
Determine whether the issue is a responsibility violation, tangled control flow, or misplaced logic/coupling.
Step 3
Apply the matching remedy
Use SRP-driven extraction, layering, moving behavior to data, or dependency inversion depending on the pattern found.
Step 4
Prevent recurrence
Add code review checks and architectural guardrails so the same anti-pattern does not reappear.
What Interviewer Expects
- Ability to name and briefly describe at least 3-4 distinct anti-patterns
- Understanding of why each one increases coupling or reduces maintainability
- Knowledge of the standard remedy for each pattern named
- Awareness that these emerge gradually, not from a single bad decision
Common Mistakes
- Naming only one anti-pattern when asked for "common" ones
- Conflating distinct anti-patterns (e.g. God Object and spaghetti code) as identical
- Not connecting each anti-pattern to a concrete remedy
- Describing anti-patterns as unavoidable rather than preventable with review discipline
Best Answer (HR Friendly)
“Common OOP anti-patterns are recurring design mistakes like a God Object that does too much, spaghetti code with tangled logic, an anemic model where data and behavior are split apart, and circular dependencies between classes. Knowing the names helps a team spot them early in code review and apply the right fix, like splitting responsibilities or introducing clearer interfaces, before the problem spreads through the codebase.”
Code Example
// Anti-pattern: circular dependency between two classes
class Customer {
Order lastOrder;
void setLastOrder(Order o) { this.lastOrder = o; }
}
class Order {
Customer customer; // depends directly on Customer
Order(Customer customer) {
this.customer = customer;
customer.setLastOrder(this); // Customer depends back on Order
}
}
// Refactored: break the cycle with a one-directional relationship
class Customer {
private final String id;
Customer(String id) { this.id = id; }
String getId() { return id; }
}
class Order {
private final String customerId; // reference by id, not a live back-link
Order(String customerId) { this.customerId = customerId; }
}
class OrderHistory {
// owns the association instead of the entities owning each other
java.util.Map<String, java.util.List<Order>> ordersByCustomer = new java.util.HashMap<>();
}Follow-up Questions
- How would you detect a God Object or circular dependency using static analysis tools?
- Why does favoring composition over inheritance help avoid several of these anti-patterns?
- How does dependency inversion help break a circular dependency?
- What code-review practices help catch these anti-patterns early?
MCQ Practice
1. Which of the following is a recognized OOP anti-pattern?
The anemic domain model, where data and behavior are split apart, is a well-known OOP anti-pattern; the others are good practices/principles.
2. What do most OOP anti-patterns have in common?
Nearly every OOP anti-pattern increases coupling or hidden dependency, making isolated change riskier.
3. A circular dependency between two classes is typically resolved by?
Breaking the direct mutual reference — often via a mediator or an id-based reference — removes the cycle.
Flash Cards
Name four common OOP anti-patterns. — God Object, spaghetti code, anemic domain model, circular dependency.
What do they share? — They all increase coupling and reduce safe changeability.
Why name anti-patterns at all? — A shared vocabulary lets teams flag and fix design problems precisely in code review.
General remedy theme? — Apply SRP, favor composition, introduce clear interfaces, keep behavior near its data.