Skip to main content

Overuse of Patterns: Patternitis

Using design patterns everywhere, even when simple solutions work better.

TL;DR

Patternitis is the obsession with applying design patterns to every situation, even when a simple solution is better. A Factory pattern to create a single object type. A Strategy pattern when an if-statement suffices. Patterns add complexity: more classes, more indirection, more code to understand. Use patterns only when they solve a real problem, not because you learned them recently. Over-engineering with patterns makes code harder to read and maintain.

Learning Objectives

You will be able to:

  • Identify when patterns add value vs. complexity
  • Understand the cost-benefit tradeoff of design patterns
  • Recognize situations where simple solutions are better
  • Apply patterns only when justified
  • Refactor over-engineered code back to simplicity
  • Know when to use and not use common patterns

Motivating Scenario

You join a team that just learned about design patterns. The codebase is now a museum of patterns:

Creating a User requires:

  • UserFactory (creates Users)
  • UserFactoryInterface (UserFactory implements it)
  • UserBuilderPattern (alternative builder)
  • UserSingletonFactory (singleton variant)

Creating a user now requires:

UserFactory factory = UserFactoryImplementation.getInstance();
User user = factory.createUser("john", "john@example.com");

Instead of simply:

User user = new User("john", "john@example.com");

Four classes for what should be a constructor call. The team spent time building this "architecture" instead of shipping features.

Core Explanation

What is Patternitis?

The compulsive application of design patterns beyond their useful scope. Patterns treated as rules rather than tools. Learning a pattern and seeing it everywhere.

Why Patterns Exist

Patterns solve specific problems:

  • Factory: When object creation is complex (multiple subclasses, conditional logic)
  • Strategy: When algorithm choice varies at runtime
  • Observer: When changes in one object must trigger updates in many
  • Decorator: When adding behavior dynamically

When Patterns Create Waste

  • Creating one User with User constructor: no need for Factory
  • One algorithm with if/else: no need for Strategy
  • Defensive coding "we might change it later": YAGNI (You Aren't Gonna Need It)
  • The problem doesn't exist yet: speculative abstraction

The Cost of Patterns

  • More classes to understand and maintain
  • More indirection (harder to trace code flow)
  • More files to change for simple modifications
  • Larger cognitive load for new developers
  • Usually unnecessary until the problem actually manifests

Code Examples

user_module.py
from abc import ABC, abstractmethod
from enum import Enum

# Over-engineered for a simple problem

class UserType(Enum):
REGULAR = "regular"
PREMIUM = "premium"

class UserBuilder(ABC):
"""Abstract builder"""
@abstractmethod
def build(self) -> 'User':
pass

class RegularUserBuilder(UserBuilder):
"""Concrete builder for regular users"""
def __init__(self, name, email):
self.name = name
self.email = email

def build(self):
return User(self.name, self.email, UserType.REGULAR)

class PremiumUserBuilder(UserBuilder):
"""Concrete builder for premium users"""
def __init__(self, name, email, plan):
self.name = name
self.email = email
self.plan = plan

def build(self):
return User(self.name, self.email, UserType.PREMIUM, plan=self.plan)

class UserFactory(ABC):
"""Abstract factory"""
@abstractmethod
def create_user(self) -> User:
pass

class RegularUserFactory(UserFactory):
"""Factory for regular users"""
def __init__(self, name, email):
self.builder = RegularUserBuilder(name, email)

def create_user(self):
return self.builder.build()

class PremiumUserFactory(UserFactory):
"""Factory for premium users"""
def __init__(self, name, email, plan):
self.builder = PremiumUserBuilder(name, email, plan)

def create_user(self):
return self.builder.build()

class User:
"""The actual user class (was simple)"""
def __init__(self, name, email, user_type, plan=None):
self.name = name
self.email = email
self.user_type = user_type
self.plan = plan

# Usage is now incredibly complex:
factory = RegularUserFactory("john", "john@example.com")
user = factory.create_user()

# Instead of: user = User("john", "john@example.com", UserType.REGULAR)

# This added:
# - 4 new classes
# - 50+ lines of boilerplate
# - Multiple layers of indirection
# - For a simple problem!

Patterns and Pitfalls

Why Patternitis Develops

1. Pattern Learning Enthusiasm New developer learns Factory pattern, sees it as a solution for everything.

2. "Enterprise" Cargo Cult Big systems use patterns, therefore patterns = sophistication.

3. Resume-Driven Development "I'll use patterns to impress in code reviews."

4. Defensive Programming "We might need this flexibility someday." Patterns built speculatively.

When Patterns Are Justified

  • Complexity: Actual problem requires the pattern
  • Reuse: Same pattern used in 2-3 places
  • Clarity: Pattern clarifies intent more than simple code
  • Flexibility: Actual requirement (not speculative) for flexibility

When This Happens / How to Detect

Red Flags:

  1. More classes than necessary for the problem
  2. Interfaces with single implementation
  3. Factory that does almost nothing
  4. Strategy with two strategies for simple conditional
  5. Comments like "Uses Factory pattern for flexibility"
  6. 5+ files needed for a simple operation
  7. "We might need to support X in the future" (speculative)

How to Fix / Refactor

Step 1: Identify Over-Engineered Patterns

Mark code using patterns with no clear benefit.

Step 2: Inline or Remove

Replace:

factory.create() → new Class()
strategy.execute() → simple method call

Step 3: Add Patterns Back When Needed

When you actually need flexibility (2-3 use cases), add the pattern back.

Design Review Checklist

  • Can this problem be solved without patterns?
  • Are there 2-3 real use cases for this pattern?
  • Does the pattern reduce complexity or add it?
  • Could a new developer understand this without pattern knowledge?
  • Is the pattern solving a current problem or a speculative one?
  • Would removing this pattern break functionality?
  • Is there clear documentation why the pattern is needed?
  • Are interfaces/abstract classes used or just indirection?
  • Does the code have more classes than necessary?

Showcase

Signals of Patternitis

  • Factory for single object type
  • Interfaces with one implementation
  • Strategy for if-else logic
  • 5+ classes for simple operation
  • 'Flexibility' as justification without real use cases
  • Direct object construction when simple
  • Patterns used for 2-3 actual use cases
  • Clear justification for each pattern
  • Simple solution when it works
  • Patterns added when needed, not speculatively

Self-Check

  1. Can you explain why this pattern is needed? If your answer is "flexibility someday," it's patternitis.

  2. Are there 2-3 places using this pattern? If only one, you don't need the pattern.

  3. Would removing this pattern break functionality? If no, it's over-engineering.

Next Steps

  • Audit: Identify patterns in your codebase with single use case
  • Remove: Inline simple patterns, use direct construction
  • Simplify: Replace Strategy with functions where applicable
  • Test: Ensure functionality doesn't change
  • Document: If keeping patterns, document why

One Takeaway

info

Use the simplest solution that works. Add patterns only when you have 2-3 real use cases, not when you imagine needing them.

References

  1. Design Patterns Reference ↗️
  2. YAGNI: You Aren't Gonna Need It ↗️
  3. Code Smell: Over-Engineering ↗️
  4. Simplification Techniques ↗️