10 Python Anti-Patterns That Are Killing Your Code Quality

Avoid these 10 common Python anti-patterns that silently degrade your code quality — and learn what to do instead.

10 Python Anti-Patterns That Are Killing Your Code Quality
Photo by Piotr Bednarczuk on Unsplash

Bad habits lead to bad code.

10 Python Anti-Patterns That Are Killing Your Code Quality

Python is a beautiful language — concise, readable, and powerful. But it’s also deceptively easy to write bad code in Python. Whether you’re a beginner or have years of experience, it’s shockingly easy to fall into common traps that silently degrade your code quality.

In this article, we’ll walk through 10 Python anti-patterns — the coding habits and structures that might work, but at the cost of readability, maintainability, or performance.

Let’s make sure you’re not unintentionally sabotaging your own code.

1. Using Mutable Default Arguments

def append_to_list(value, my_list=[]): 
    my_list.append(value) 
    return my_list

Why it’s bad: The default list is shared across function calls. This can lead to unexpected behavior.

Better:

def append_to_list(value, my_list=None): 
    if my_list is None: 
        my_list = [] 
    my_list.append(value) 
    return my_list

Always avoid using mutable objects like lists or dictionaries as default argument values.

2. Catching Generic Exceptions

try: 
    do_something() 
except Exception: 
    pass

Why it’s bad: This swallows all exceptions, including ones you probably didn’t intend to ignore, making debugging a nightmare.

Better:

try: 
    do_something() 
except ValueError: 
    handle_value_error()

Be specific about the exceptions you’re handling. Broad catches should be used sparingly, and only when you explicitly want to catch all errors (and log them).

3. Writing Long, Monolithic Functions

A 150-line function is not a badge of honor.

Why it’s bad: Hard to test, hard to understand, hard to reuse.

Better: Break your logic into smaller, purpose-driven functions. Use descriptive names.

Good code reads like a story. Modularize your logic.

4. Reinventing the Wheel

def is_even(n): 
    return n % 2 == 0

Okay, that one’s harmless. But reinventing max(), sum(), or writing your own JSON parser? Not so much.

Why it’s bad: Python has a rich standard library. Use it.

Leverage built-in functions and libraries instead of rolling your own, unless you have a very specific reason.

5. Abusing List Comprehensions

[print(x) for x in items]

Why it’s bad: You’re using a list comprehension for side effects, not to create a list. That’s not what it’s for.

Better:

for x in items: 
    print(x)

Use list comprehensions for constructing lists. Use loops for side effects.

6. Overusing lambda Functions

sorted(items, key=lambda x: x[1])

This is fine, but when your lambda starts to look like a mini script, it’s time to refactor:

key=lambda x: (x[1] * 2 if x[0] == 'foo' else x[1] - 3)

Better:

def custom_sort_key(item): 
    if item[0] == 'foo': 
        return item[1] * 2 
    return item[1] - 3 
 
sorted(items, key=custom_sort_key)

When your lambda becomes complex, give it a proper function name.

7. Hardcoding Values Everywhere

if user_role == "admin": 
    # ...

Why it’s bad: Magic strings and numbers scatter throughout your code, making it brittle and error-prone.

Better:

ADMIN_ROLE = "admin" 
 
if user_role == ADMIN_ROLE: 
    # ...

Use constants. It makes your code self-documenting and easier to maintain.

8. Ignoring Virtual Environments

Installing packages globally? 😬

Why it’s bad: You risk polluting your system Python and creating conflicts between project dependencies.

Better:

python -m venv venv 
source venv/bin/activate

Always use a virtual environment for project isolation.

9. Overcomplicating With Inheritance

class Dog(Animal): 
    # 100 lines of overridden methods

Why it’s bad: Inheritance chains can quickly become tangled and fragile.

Better: Use composition over inheritance when it makes more sense.

class Dog: 
    def __init__(self, walker): 
        self.walker = walker

Inheritance is not always the answer. Ask yourself: “Could this be composition instead?”

10. Neglecting Code Formatting

If you’re still debating spaces vs. tabs in 2025… it’s time.

Why it’s bad: Inconsistent code formatting wastes time in code reviews and causes merge conflicts.

Better: Use tools like black, isort, and flake8 to keep your code clean.

black main.py

Let your formatter handle the style. You focus on logic.


Final Thoughts

Writing clean, maintainable Python isn’t about memorizing best practices — it’s about developing a mindset of clarity, simplicity, and intent. These anti-patterns aren’t crimes, but if left unchecked, they silently erode the quality of your codebase.

Take a few minutes to audit your current projects. You might be surprised at what you find.


Like this article?
Follow me for more Python insights, code improvement tips, and developer best practices.
Got your own Python pet peeves? Drop them in the comments — I’d love to hear them!

Photo by Arno Senoner on Unsplash