Skip to content

DRY Principle

Don't Repeat Yourself - write code once, use it everywhere!

The DRY (Don’t Repeat Yourself) principle is one of the most fundamental principles in software development. It states that every piece of knowledge must have a single, unambiguous representation within a system.

DRY Principle helps you:

  • Reduce duplication - Write code once, use it everywhere
  • Easier maintenance - Change code in one place
  • Consistency - Same logic behaves the same everywhere
  • Less bugs - Fix bugs once, not in multiple places
  • Better readability - Less code to read and understand
Diagram

Without DRY Principle, you might:

  • Duplicate code - Same logic repeated in multiple places
  • Inconsistent behavior - Same logic implemented differently
  • Maintenance nightmare - Need to update code in many places
  • More bugs - Fix bugs in multiple places, easy to miss some
  • Harder to test - Test same logic multiple times
Diagram

Let’s see a simple example showing the problem and solution:

Diagram
bad_validation.py
# ❌ Without DRY - Code duplication everywhere!
def register_user(email: str, password: str):
# Validation logic duplicated
if not email or "@" not in email:
raise ValueError("Invalid email")
if not password or len(password) < 8:
raise ValueError("Password must be at least 8 characters")
# ... registration logic
def login_user(email: str, password: str):
# Same validation logic duplicated!
if not email or "@" not in email:
raise ValueError("Invalid email")
if not password or len(password) < 8:
raise ValueError("Password must be at least 8 characters")
# ... login logic
def reset_password(email: str, new_password: str):
# Same validation logic duplicated again!
if not email or "@" not in email:
raise ValueError("Invalid email")
if not new_password or len(new_password) < 8:
raise ValueError("Password must be at least 8 characters")
# ... reset logic
# Problems:
# - Validation logic repeated 3 times
# - If validation rules change, need to update 3 places
# - Easy to make mistakes or forget to update all places
Diagram
dry_validation.py
# ✅ With DRY - Validation logic in one place!
class Validator:
"""Single source of truth for validation logic"""
@staticmethod
def validate_email(email: str) -> None:
"""Validate email - defined once, used everywhere"""
if not email or "@" not in email:
raise ValueError("Invalid email")
@staticmethod
def validate_password(password: str) -> None:
"""Validate password - defined once, used everywhere"""
if not password or len(password) < 8:
raise ValueError("Password must be at least 8 characters")
def register_user(email: str, password: str):
# Use validation from single source
Validator.validate_email(email)
Validator.validate_password(password)
# ... registration logic
def login_user(email: str, password: str):
# Use same validation - no duplication!
Validator.validate_email(email)
Validator.validate_password(password)
# ... login logic
def reset_password(email: str, new_password: str):
# Use same validation - no duplication!
Validator.validate_email(email)
Validator.validate_password(new_password)
# ... reset logic
# Benefits:
# - Validation logic in one place
# - Change validation rules once, affects all uses
# - Consistent behavior everywhere
# - Easier to test - test validation once

Here’s a more realistic example showing DRY in action:

dry_database.py
# ❌ Without DRY - Database connection logic duplicated
class UserService:
def get_user(self, user_id: int):
# Connection logic duplicated
connection = create_connection()
try:
# ... query logic
return user
finally:
connection.close()
def create_user(self, user_data: dict):
# Same connection logic duplicated!
connection = create_connection()
try:
# ... insert logic
return user
finally:
connection.close()
# ✅ With DRY - Database connection logic in one place
class DatabaseManager:
"""Single source of truth for database operations"""
@staticmethod
def execute_query(query: str, params: tuple = None):
"""Execute query - handles connection lifecycle"""
connection = create_connection()
try:
cursor = connection.cursor()
cursor.execute(query, params)
return cursor.fetchall()
finally:
connection.close()
class UserService:
def get_user(self, user_id: int):
# Use database manager - no duplication!
results = DatabaseManager.execute_query(
"SELECT * FROM users WHERE id = %s",
(user_id,)
)
return results[0] if results else None
def create_user(self, user_data: dict):
# Use same database manager - no duplication!
DatabaseManager.execute_query(
"INSERT INTO users (name, email) VALUES (%s, %s)",
(user_data['name'], user_data['email'])
)

Apply DRY Principle when:

Same logic appears multiple times - Extract to function/class
Business rules are repeated - Centralize in one place
Configuration values duplicated - Use constants/config
Similar code patterns - Create reusable abstractions
Data structures repeated - Define once, reuse

Diagram

Don’t over-apply DRY when:

Code is similar but different - Don’t force abstraction
Premature abstraction - Wait until you see actual duplication
Over-engineering - Simple duplication might be fine
Performance critical - Sometimes duplication is faster


  • DRY Principle = Don’t Repeat Yourself
  • Single source of truth - Every piece of knowledge in one place
  • Easier maintenance - Change once, affects all uses
  • Consistency - Same behavior everywhere
  • Balance - Don’t over-abstract, eliminate real duplication

Remember: DRY is about eliminating real duplication, not creating unnecessary abstractions. Use it wisely! 🎯