7 Python Mistakes Devs Still Make in 2025 (And I Still Make #4)

Python is forgiving. That's partly why we love it โ€” and exactly why these mistakes are so easy to miss until they blow up in production at 3 AM. Even with the rise of AI coding assistants, these "classic" errors continue to haunt codebases worldwide.

I've been writing Python for years, reviewed hundreds of pull requests, and I still occasionally catch myself making these same errors. In 2025, with Python 3.12+ features available, some of these are even more avoidable. Here are 7 mistakes that I see over and over in modern software development.

Mistake #1: Using Mutable Default Arguments

This is a Python classic that bites every beginner and even some seniors. In Python, default arguments are evaluated once at the time of function definition, not every time the function is called.

# โŒ WRONG
def add_user(name, users=[]):
    users.append(name)
    return users

# ๐Ÿ›‘ Result of calling add_user('Alice') then add_user('Bob'):
# ['Alice', 'Bob'] <- The list persists across calls!

The fix is simple: always use None as the default for mutable types (lists, dicts, sets).

# โœ… CORRECT
def add_user(name, users=None):
    if users is None:
        users = []
    users.append(name)
    return users
Advertisement
Advertisement

Mistake #2: The "Manual Close" Trap

Still seeing developers manually opening and closing files in 2025. If your code crashes between open() and close(), the file handle remains open, leading to memory leaks and resource exhaustion.

# โŒ WRONG
f = open('data.json', 'r')
data = f.read()
f.close() # Never runs if an error occurs above

The with statement (Context Manager) is non-negotiable. It guarantees that the file is closed even if an exception is raised.

# โœ… CORRECT
with open('data.json', 'r') as f:
    data = f.read()

Mistake #3: The Silent Killer (Bare Except)

Using except: without a specific exception type is the fastest way to make your code impossible to debug. It catches everything, including KeyboardInterrupt (Ctrl+C), meaning you can't even stop your own script.

# โŒ WRONG
try:
    process_data()
except:
    pass # Total darkness. You'll never know why it failed.

Be specific. If you're expecting a potential network error, catch RequestException. If you truly need to catch everything, at least catch Exception (which excludes system signals) and log it.

Mistake #4: Building Strings with +=

I still do this ๐Ÿ˜… and I have to catch myself. Strings in Python are immutable. Every time you do string += "new content", Python creates a completely new string object in memory and copies the old content into it. In a large loop, this becomes an O(n^2) nightmare for performance.

# โŒ SLOW
final_str = ""
for msg in huge_list:
    final_str += msg

The Pythonic (and much faster) way is to collect parts in a list and use .join().

# โœ… FAST
final_str = "".join(huge_list)

Mistake #5: Living in the "Typeless" Past

In 2025, if you aren't using Type Hints, you are building technical debt. Type hints (introduced in PEP 484) are not just documentation; they allow your IDE (VS Code, PyCharm) to catch bugs before you even run the code.

# โŒ AMBIGUOUS
def get_total(prices):
    return sum(prices)

# โœ… CLEAR
def get_total(prices: list[float]) -> float:
    return sum(prices)

Mistake #6: Over-Engineering Simple Loops

New devs often write five lines of code for something that Python can do in one. Manual list filtering is a waste of time and harder to read.

# โŒ VERBOSE
active_users = []
for u in users:
    if u.is_active:
        active_users.append(u.name)

List comprehensions are faster (written in C) and more readable once you get used to them.

# โœ… PYTHONIC
active_users = [u.name for u in users if u.is_active]

Mistake #7: Misunderstanding is vs. ==

== checks for **equality** (do they have the same value?). is checks for **identity** (are they literally the same object in memory?).

list1 = [1, 2, 3]
list2 = [1, 2, 3]

print(list1 == list2) # True
print(list1 is list2) # False (they are separate lists)

Only use is when comparing to singletons like None, True, or False.

Frequently Asked Questions

Which Python linter catches these mistakes?

I highly recommend Ruff. It's written in Rust and is incredibly fast. It combined the rules from Flake8, Black, Isort, and more. It will flag almost all the mistakes listed above automatically in your editor.

Are list comprehensions always better?

Not always. If the logic is too complex (e.g., nested loops with three conditions), a standard for loop is easier for humans to read. Code readability always trumps micro-optimizations.

Is catching Exception safe?

It's safer than a bare except: because it won't trap SystemExit or KeyboardInterrupt, allowing you to actually stop the program. However, you should still always try to be as specific as possible.

"Write code as if the person who ends up maintaining it is a violent psychopath who knows where you live." โ€” John Woods

Avoid these mistakes, and the psychopath (who might be you in six months) will be much happier. Happy coding!

Disclaimer: "All content is for educational use only. Snapdo and its authors are not liable for any financial losses, data loss, or hardware damage."

ZJ

Written by ZayJII

Developer, trader, and realist. Writing tutorials that actually work.

Advertisement
Advertisement