Dead Code Patterns Reference
This document catalogs common patterns of dead code and how to identify them.
Unused Functions and Methods
Pattern 1: Orphaned Helper Functions
Description: Utility functions that were written but never actually used.
Indicators:
- Function defined but no calls anywhere in codebase
- Often in utility modules or helper files
- May have been written "just in case" but never needed
Example:
# utils.py
def format_date(date):
"""Format date to ISO string."""
return date.isoformat() # Never called anywhere
def parse_date(date_str):
"""Parse ISO date string.""" # This one IS used
return datetime.fromisoformat(date_str)
Detection: AST analysis showing function definition but no call sites.
Pattern 2: Refactoring Leftovers
Description: Old functions that were replaced but not removed.
Indicators:
- Function name suggests old implementation (e.g.,
process_data_old,legacy_handler) - Similar function with newer name exists
- Comments like "deprecated" or "use X instead"
Example:
def calculate_total_v1(items):
# Old implementation - DO NOT USE
return sum(item.price for item in items)
def calculate_total(items, tax_rate=0.1):
# New implementation with tax
return sum(item.price for item in items) * (1 + tax_rate)
Pattern 3: Test Helpers Never Used
Description: Test utility functions that aren't called by any tests.
Indicators:
- Defined in test files or conftest.py
- Not used by any test functions
- May be fixtures that were never referenced
Example:
# test_utils.py
def create_sample_user(): # Never used
return User(name="Test", email="test@example.com")
def create_sample_order(): # This one IS used in tests
return Order(user_id=1, total=100)
Unused Imports
Pattern 4: Removed Usage
Description: Import was needed but code using it was removed.
Example:
import os
import sys
from datetime import datetime # Used
from pathlib import Path # Not used anymore
def get_timestamp():
return datetime.now()
# Previously had code using Path but it was removed
Pattern 5: Overly Broad Imports
Description: Importing entire module when only one function is needed, or vice versa.
Example:
import json
import re # Never used
from typing import List, Dict, Optional, Tuple # Only List is used
def parse_data(data: List[str]):
return json.loads(data[0])
Pattern 6: Duplicate Imports
Description: Same module imported multiple times or in different forms.
Example:
import os
from os import path # Redundant - can use os.path
from os.path import join # Actually used
def get_file_path(dir, filename):
return join(dir, filename)
Unreachable Code
Pattern 7: Code After Return
Description: Code that appears after a return statement in the same block.
Example:
def process(data):
if not data:
return None
print("Data is empty") # Unreachable
return data.upper()
Pattern 8: Impossible Conditions
Description: Conditions that can never be true due to previous logic.
Example:
def validate_age(age):
if age < 0:
return False
if age >= 0 and age < 18:
return "minor"
if age < 0: # Impossible - already checked above
return "invalid"
return "adult"
Pattern 9: Always-False Conditions
Description: Conditions that are always False due to constants or previous checks.
Example:
def check_value(x):
if x > 10:
return "high"
elif x > 5:
return "medium"
elif x > 10: # Impossible - already handled above
return "very high"
else:
return "low"
Pattern 10: Code After Break/Continue/Raise
Description: Statements after control flow statements in loops.
Example:
def find_item(items, target):
for item in items:
if item.id == target:
return item
print(f"Found: {item}") # Unreachable
return None
Redundant Code
Pattern 11: Redundant Conditions
Description: Conditions that are always true or already checked.
Example:
def process_user(user):
if user is not None:
if user: # Redundant - already checked not None
return user.name
return "Unknown"
Better:
def process_user(user):
if user:
return user.name
return "Unknown"
Pattern 12: Unnecessary Else After Return
Description: Else block that's unnecessary because previous block always returns.
Example:
def get_status(value):
if value > 0:
return "positive"
else: # Unnecessary else
return "non-positive"
Better:
def get_status(value):
if value > 0:
return "positive"
return "non-positive"
Pattern 13: Redundant Boolean Operations
Description: Unnecessary boolean comparisons or conversions.
Example:
def is_valid(data):
if data is not None:
return True
else:
return False
# Better:
def is_valid(data):
return data is not None
Pattern 14: Duplicate Logic
Description: Same logic repeated in multiple places.
Example:
def process_a(data):
if not data:
return []
result = []
for item in data:
if item.active:
result.append(item.name)
return result
def process_b(data):
if not data:
return []
result = []
for item in data:
if item.active: # Duplicate logic
result.append(item.name)
return result
Unused Variables
Pattern 15: Assigned But Never Read
Description: Variables assigned but never used afterwards.
Example:
def calculate(a, b):
total = a + b # Never used
result = a * b
return result
Pattern 16: Loop Variables Never Used
Description: Loop iteration variables that aren't used in the loop body.
Example:
# Bad
for i in range(10):
print("Hello") # i is never used
# Better - use underscore to indicate intentionally unused
for _ in range(10):
print("Hello")
Pattern 17: Function Parameters Never Used
Description: Parameters declared but not used in function body.
Example:
def greet(name, title): # title is never used
return f"Hello, {name}!"
Detection Strategies
Static Analysis
AST-based detection:
- Parse Python code into Abstract Syntax Tree
- Track all definitions (functions, imports, variables)
- Track all usages (calls, references)
- Report definitions without usages
Tools:
vulture- Find unused code in Pythonautoflake- Remove unused imports and variablespylint- Detects various forms of dead code- Custom AST analysis scripts
Dynamic Analysis
Coverage-based detection:
- Run test suite with coverage enabled
- Identify lines never executed
- May indicate dead code (or missing tests)
Tools:
coverage.py- Measure code coveragepytest-cov- Coverage plugin for pytest
Manual Review
Code review indicators:
- Functions with no callers
- Imports highlighted as unused by IDE
- Comments like "TODO: remove this"
- Version control history showing code not modified in years
- Duplicate or very similar functions
Special Cases and Exceptions
When "Dead" Code Isn't Really Dead
1. Public API Functions
- May be unused internally but called by external users
- Keep if part of documented public API
2. Plugin/Hook Functions
- Called dynamically via strings or introspection
- Examples: Django signal handlers, pytest fixtures
3. Decorators and Metaclasses
- May be used but hard to detect statically
- Check for
@usage or metaclass assignments
4. Template/Example Code
- Intentionally unused examples in documentation
- Should be in docs directory, not main codebase
5. Future/Planned Features
- Code written in advance for planned features
- Should have clear comments and tickets
6. CLI Entry Points
- Functions called from command line or config
- Check setup.py entry_points
7. Dynamically Called Functions
# May appear unused but called via getattr
handlers = {
'create': create_handler,
'update': update_handler,
'delete': delete_handler,
}
action = request.get('action')
handler = handlers.get(action)
handler(data) # Dynamic dispatch
Prioritization
Not all dead code is equally important to remove:
High Priority:
- Unused imports (easy to remove, reduce namespace pollution)
- Unreachable code (clearly bugs or confusion)
- Duplicate logic (maintenance burden)
Medium Priority:
- Unused utility functions (clutter but low risk)
- Redundant conditions (minor performance impact)
- Unused variables (code smell but harmless)
Low Priority:
- Private helper functions never called (may be OK)
- Code in deprecated modules (being phased out anyway)
- Example/template code in proper location