🧩 Section 2 · Question #16

What anti-patterns do you know

An Anti-Pattern is a common, recurring design or coding practice that initially seems intuitive or convenient, but ultimately produces severe negative consequences: increased ma...


🟢 Junior Level

An Anti-Pattern is a common, recurring design or coding practice that initially seems intuitive or convenient, but ultimately produces severe negative consequences: increased maintenance costs, subtle bugs, performance degradation, and insurmountable technical debt. Unlike a simple coding error or syntax bug, an anti-pattern has a recognized structure and a documented, repeatable refactoring solution.


Real-World Analogies

  • Taking Antibiotics for the Common Cold: Appears to be an aggressive, proactive medical response, but in reality, antibiotics do not kill viruses; instead, they destroy beneficial gut flora and cultivate antibiotic-resistant superbugs.
  • Fixing a Leaking Pipe with Duct Tape: Fixes the symptom for ten minutes, but hides the underlying structural fracture and guarantees a catastrophic ceiling flood later.

Common Code-Level Anti-Patterns

1. God Object (The Blob)

A single monolithic class assumes far too many responsibilities (thousands of lines of code, dozens of injected dependencies), reducing all other surrounding classes to passive data containers:

// ❌ ANTI-PATTERN: God Object does everything
public class OrderManager {
    public void validateCart() { /* ... */ }
    public void calculateDiscounts() { /* ... */ }
    public void processPayment() { /* ... */ }
    public void generatePdfInvoice() { /* ... */ }
    public void sendEmailConfirmation() { /* ... */ }
    public void backupToDatabase() { /* ... */ }
}

// ✅ REFACTORING: Decompose according to the Single Responsibility Principle (SRP)
// PaymentService, InvoiceGenerator, NotificationService, OrderRepository

2. Spaghetti Code

A program structure with convoluted control flow, deeply nested if-else conditionals, unstructured loops, and arbitrary jumps, making readability and unit testing virtually impossible.

3. Copy-Paste Programming (Shotgun Surgery)

Duplicating code fragments across different classes instead of extracting reusable methods or shared utilities. Fixing a bug in one place leaves 10 copied locations vulnerable (Shotgun Surgery).

4. Magic Numbers & Strings

Hardcoding unnamed numeric literals and raw strings directly in business logic:

// ❌ ANTI-PATTERN: Cryptic numbers and raw strings
if (user.getStatus().equals("ACTIVE") && duration > 86400) { ... }

// ✅ REFACTORING: Named constants and Enum types
private static final int SECONDS_PER_DAY = 86_400;
if (user.getStatus() == UserStatus.ACTIVE && duration > SECONDS_PER_DAY) { ... }

🟡 Middle Level

High-Yield Object-Oriented Anti-Patterns

1. Golden Hammer (Law of the Instrument)

“If all you have is a hammer, everything looks like a nail.”

Forcing a familiar technology, design pattern, or framework onto every problem regardless of suitability.

  • Examples: Deploying Kafka for a batch job moving 5 records a day; designing a Kubernetes microservice cluster for a 10-user internal CRUD tool; forcing the Visitor pattern where simple polymorphism suffices.

2. Poltergeist (Gypsy Wagon)

Short-lived, stateless classes with no independent state whose only function is to invoke another class and disappear. They introduce meaningless layers of indirection that complicate call stack debugging:

// ❌ ANTI-PATTERN: Useless pass-through class
public class OrderProcessorProxyHelper {
    public void execute(Order order) {
        new RealOrderProcessor().process(order);
    }
}

3. Sequential Coupling (Temporal Coupling)

Designing a class whose methods must be executed in a strict, unwritten chronological order, verified only at runtime:

// ❌ ANTI-PATTERN: Undocumented temporal dependency
ReportGenerator report = new ReportGenerator();
report.initTemplate();
report.loadData();
report.renderPdf(); // Forgetting initTemplate() throws NullPointerException at runtime!

// ✅ REFACTORING: Step Builder, Factory, or mandatory constructor injection
ReportGenerator report = ReportGenerator.fromTemplate(template).withData(data);
report.renderPdf();

4. Anemic Domain Model vs Rich Domain Model

Entities are reduced to dumb data holders (fields with getters and setters), while business invariants and business logic are scattered across massive 3,000-line @Service procedural classes.

  • Why It Is an Anti-Pattern (Martin Fowler): Violates fundamental OOP encapsulation. Business validation rules leak across layers, and entity invariants cannot be guarded.
  • Remediation: Rich Domain Model (Domain-Driven Design / DDD), where entities guard their own state transitions (order.cancel(), account.withdraw(amount)).

5. Lava Flow (Dead / Zombie Code)

Layers of obsolete, dead code that developers are afraid to delete (“It somehow works, don’t touch it or production might collapse”). Usually caused by a lack of automated test suites and frequent team turnover.


Architectural Symptoms and Remedies

Anti-Pattern Primary Code Smell Architectural Consequence Remediation
God Object Class $> 1,000$ lines, $> 20$ dependencies Merge conflicts, impossible to unit test SRP decomposition, Facade pattern
Sequential Coupling Implicit temporal call order Runtime IllegalStateException crashes Step Builder, Constructor Injection
Golden Hammer Oversized tech stack for simple requirements Ballooning cloud infrastructure costs Principles of KISS, YAGNI
Anemic Model Entities contain only getters/setters Logic duplicated across multiple services Rich Domain Model (DDD invariants)
Lava Flow Commented-out code, orphaned @Deprecated Repository clutter, fear of refactoring Integration tests (JaCoCo) + dead code purge

🔴 Senior Level

Enterprise Architecture & Spring Framework Anti-Patterns

1. Big Ball of Mud

A software architecture that has completely eroded: boundaries between layers are nonexistent, controllers bypass services to execute database updates directly, and circular package dependencies abound.

  • Automated CI/CD Defense: ArchUnit:
    @ArchTest
    public static final ArchRule no_controllers_in_repositories = 
        noClasses().that().resideInAPackage("..repository..")
                   .should().dependOnClassesThat().resideInAPackage("..controller..");
    

2. Cargo Cult Programming in Spring Boot

Blindly copy-pasting annotations and configurations without understanding their runtime mechanics:

  • @Transactional on Long Network Calls: Annotating methods that execute slow HTTP/REST calls or send emails via SMTP. The database connection from the HikariCP pool remains held open throughout the network wait, causing connection pool exhaustion under moderate load.
  • Field Injection (@Autowired private Service service;): Prevents unit testing without reflection or Spring context, hides class dependencies, and enables circular dependency anti-patterns. Constructor injection is the only robust standard.
  • Uncontrolled @Async without a Custom TaskExecutor: Default Spring @Async falls back to SimpleAsyncTaskExecutor, which does not use a thread pool but spawns a brand new OS thread per task. A traffic spike triggers OutOfMemoryError: unable to create new native thread.

3. Distributed Monolith

A system decomposed into dozens of microservices that remain tightly coupled via synchronous HTTP/gRPC call chains and a shared centralized database:

  • Flaws: Cascading service outages (one slow service exhausts thread pools across the entire chain), deployment dependencies (cannot deploy Service A without simultaneous releases of B and C), and data lock-in.
  • Remediation: Asynchronous event-driven messaging (Kafka/RabbitMQ), Database-per-service pattern, and strict Bounded Contexts.

4. Premature Optimization

“Premature optimization is the root of all evil in programming.” — Donald Knuth

Introducing manual thread synchronization, hand-crafted object pools, and bitwise hacks before profiling with production-grade tools (such as async-profiler or JProfiler). It obfuscates code and frequently prevents HotSpot JIT (C2) compilers from applying natural optimizations like scalar replacement and loop unrolling.


Code Quality Metrics & Static Analysis

Automated tools (SonarQube, SpotBugs, Checkstyle, PMD) quantify technical debt using established mathematical metrics:

  1. Cyclomatic Complexity (McCabe): Measures the number of linearly independent execution paths through a code module. Standard threshold per method: $\le 10$.
  2. Cognitive Complexity: Measures how difficult control flow is for human engineers to understand (deeply nested loops and conditions increment the score exponentially).
  3. Package Instability ($I$): \(I = \frac{C_e}{C_a + C_e}\) Where $C_e$ (Efferent Coupling) represents outgoing dependencies and $C_a$ (Afferent Coupling) represents incoming dependencies. $I = 0$ indicates a maximally stable, resilient core package.

4 Tricky Questions

1. Why is the classic Singleton pattern often categorized as an architectural anti-pattern?

Answer: The classic GoF Singleton introduces global mutable state, which breaks unit test isolation and causes test flakiness due to shared state leaking across test runs. It conceals class dependencies inside static calls (MySingleton.getInstance()) rather than declaring them explicitly in the constructor, violating the Dependency Inversion Principle (DIP). Furthermore, a private constructor prevents mocking and polymorphic subclassing. In enterprise development, singleton lifecycle is managed externally by the IoC container (e.g., Spring’s singleton bean scope) rather than hardcoding static singletons.


2. What makes Cargo Cult usage of @Transactional around external network calls (HTTP/SMTP) so catastrophic for database connection pools?

Answer: In Spring, @Transactional acquires a physical database connection from the HikariCP connection pool before entering the method and holds it open until the transaction commits or rolls back upon method exit. If the method makes an external HTTP request, calls a third-party payment gateway, or sends an email via SMTP, network latency (which can easily spike to 5–30 seconds) keeps that database connection pinned and idle. Under concurrent traffic, all connection pool threads become locked in network wait states, causing HikariCP connection pool exhaustion and blocking unrelated CRUD operations across the entire application.


3. What is an Anemic Domain Model, and why did Martin Fowler describe it as contrary to the foundational principles of object-oriented design?

Answer: An Anemic Domain Model occurs when domain entities are reduced to passive data structures with only fields, getters, and setters, while all business validation, state transitions, and business logic reside in massive, procedural service classes. Martin Fowler criticized this because the core premise of Object-Oriented Design is the encapsulation of data and the operations that mutate that data within the same object. With an anemic model, entities cannot protect their internal invariants, business rules are duplicated across various services, and the code degrades into procedural programming disguised as Java classes.


4. How does the ArchUnit library protect enterprise codebases from degenerating into a “Big Ball of Mud”?

Answer: ArchUnit allows architects to define architectural and layering rules as executable Java unit tests that run during CI/CD builds. It analyzes Java bytecode to verify rules such as:

  • Controllers must never call repositories directly (noClasses().that().resideInAPackage("..controller..").should().dependOnClassesThat().resideInAPackage("..repository..")).
  • Packages must remain acyclic (slices().matching("..myapp.(*)..").should().beFreeOfCycles()).
  • Layered boundaries must be respected (e.g., Domain layer must have zero dependencies on Infrastructure or Web layers). If a developer breaks an architectural boundary, the build fails immediately at the test stage.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

“An Anti-Pattern is a counterproductive design practice that appears expedient in the short term but inflicts severe maintenance overhead, hidden bugs, and architectural decay. Key code-level anti-patterns include God Object (violating SRP), Sequential Coupling (implicit temporal dependencies), and Magic Numbers. Architectural anti-patterns include Golden Hammer (forcing one tool onto all problems), Cargo Cult (blindly copying annotations like @Transactional), Anemic Domain Model (procedural services over passive entities), and Distributed Monolith (tightly coupled microservices sharing a database). Prevention relies on SOLID principles, strict static analysis (SonarQube), and automated architecture unit tests (ArchUnit).”


Key Architectural Takeaways

  • God Object: Decompose massive classes by Single Responsibility.
  • Cargo Cult @Transactional: Never wrap slow external network I/O in database transactions (prevents connection pool exhaustion).
  • Sequential Coupling: Replace runtime temporal requirements with compile-time Step Builders.
  • Anemic Model: Shift domain rules and invariant checks into entities (Rich Domain Model).
  • ArchUnit: Enforce architectural layering and prevent circular package dependencies in CI/CD.

Red Flags & Anti-Patterns

  • 🚩 Claiming “I put @Transactional on every method just to be safe”: Hallmark sign of Cargo Cult programming.
  • 🚩 Defending 3,000-line God Classes with “At least all code is in one place”: Severe violation of SRP.
  • 🚩 Using Field Injection (@Autowired fields) in modern code: Always use Constructor Injection.
  • 🚩 Neglecting dead code (Lava Flow): Lack of automated tests fosters fear of refactoring.