Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected

Python

8 Common Python Mistakes That Even Junior Developers Make

Avoid the most frequent Python pitfalls — from mutable default arguments to modifying lists while iterating — with clear fixes and explanations for each bug.

June 2026 · 6 min read · 1 views · 0 hearts

Python is one of the most forgiving languages to learn, but that same flexibility can quietly lead you into traps that only reveal themselves when your code breaks at 2 AM. I’ve debugged enough junior dev projects to know the classic pitfalls by heart—here are the most common ones and how to sidestep them.

Mutable Default Arguments

You’ve probably written a function like this:

def add_item(item, items=[]):
    items.append(item)
    return items

Looks innocent. But call it twice:

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] — wait, what?

The default [] is created once when the function is defined, not each time you call it. So every call shares the same list.

Fix: Use None and create a new list inside:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Using == Instead of is for None Checks

Python has two equality operators: == checks value, is checks identity. For None, you almost always want is because None is a singleton—there’s only one of it.

x = None
if x == None:   # works, but wrong style
if x is None:   # correct and faster

Why does it matter? Custom classes can override __eq__ and make x == None return True for non-None objects. Using is avoids that surprise.

Forgetting That Variables Can Be Mutable

Lists, dictionaries, and sets are mutable. If you assign one to another variable, they point to the same object:

original = [1, 2, 3]
copy = original
copy.append(4)
print(original)  # [1, 2, 3, 4] — oops

Fix: Use .copy(), list(), or copy.deepcopy() for nested structures:

copy = original.copy()

Confusing == and is for String Comparison

is checks memory location, not value. Python sometimes caches small strings, so "hello" is "hello" might return True—until your program grows and it doesn’t. That kind of bug is a nightmare to trace.

a = "hello"
b = "hello"
print(a is b)   # True in CPython, but not guaranteed
print(a == b)   # True always

Rule of thumb: use == for strings. Use is only for None, True, False.

Not Using with for File Handling

Opening a file and forgetting to close it is a classic resource leak. In Python, files are automatically garbage-collected eventually, but you don’t control when.

f = open("data.txt", "r")
data = f.read()
# Program crashes here — file stays open

Fix: Use the context manager:

with open("data.txt", "r") as f:
    data = f.read()
# File closes automatically, even if an exception occurs

Catching Too Broad an Exception

Bare except: or except Exception: hides bugs. You might accidentally catch KeyboardInterrupt or SystemExit and swallow them.

try:
    result = risky_operation()
except:  # catches everything including Ctrl+C
    pass

Fix: Catch specific exceptions:

try:
    result = risky_operation()
except ValueError:
    handle_value_error()

Or at least re-raise unknown ones.

Modifying a List While Iterating

This is subtle. Removing elements while looping skips items:

nums = [1, 2, 3, 4, 5]
for n in nums:
    if n % 2 == 0:
        nums.remove(n)
print(nums)  # [1, 3, 5] — correct by accident, usually not

But more often you’ll get IndexError or skip elements. Better to build a new list:

nums = [n for n in nums if n % 2 != 0]

Or iterate over a copy:

for n in nums[:]:
    if n % 2 == 0:
        nums.remove(n)

Accidental Global Variable Modification Inside Functions

Inside a function, assigning to a variable creates a local variable unless you declare global. This is usually fine—but if you forget, you might think you’re modifying a global when you’re actually creating a new local.

count = 0

def increment():
    count += 1  # UnboundLocalError

def increment_global():
    global count
    count += 1  # works

The fix: use global explicitly, or better, avoid mutable global state. Pass and return values instead.

Misunderstanding for Loop Scoping in Python 2

If you’re still maintaining Python 2 code (and you shouldn’t be, but some of us have to), note that loop variables leak into the enclosing scope. Python 3 fixed this by making the loop variable local to the comprehension or loop body.

# Python 2
for x in range(5):
    pass
print(x)  # 4

In Python 3, x still exists after the loop but only holds the last iterated value. Not ideal, but at least it’s consistent.

Overusing lambda When Named Functions Are Clearer

lambda is great for short, one-off functions in map() or filter(). But when the logic gets more than one line, or you use it repeatedly, a named function improves readability.

# Confusing
process = lambda x: x**2 + 3*x - 5 if x > 0 else 0

# Clearer
def process(x):
    return x**2 + 3*x - 5 if x > 0 else 0

Also, lambda can’t contain statements (only expressions), so you can’t use print() or return inside it.


The best way to catch these mistakes is experience—and a good linter. Run pylint or flake8 on your code regularly, and watch for warnings about mutable defaults, unused variables, or broad exceptions. Python’s readability is your superpower, but only if you avoid the hidden landmines.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.