Python Basics — Common Mistakes

5 minute read

Common Mistakes

Every Python beginner makes these mistakes. Learning to spot and avoid them will save you hours of debugging.


1. Mutable Default Arguments

# ❌ BUG: Default list is shared between all calls
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['a', 'b']  ← Expected ['b']!

# ✅ FIX: Use None as default
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['b'] ✓

Why: Default arguments are evaluated once when the function is defined, not each time it’s called. Mutable defaults (lists, dicts, sets) persist between calls.


2. Modifying a List While Iterating

# ❌ BUG: Skips elements
numbers = [1, 2, 3, 4, 5, 6]
for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)
print(numbers)  # [1, 3, 5, 6]  ← 6 was skipped!

# ✅ FIX: Create a new list
numbers = [1, 2, 3, 4, 5, 6]
numbers = [n for n in numbers if n % 2 != 0]
print(numbers)  # [1, 3, 5] ✓

# ✅ FIX: Iterate over a copy
numbers = [1, 2, 3, 4, 5, 6]
for n in numbers[:]:  # [:] creates a copy
    if n % 2 == 0:
        numbers.remove(n)
print(numbers)  # [1, 3, 5] ✓

3. == vs is Confusion

# == checks VALUE equality
# is checks IDENTITY (same object in memory)

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)   # True  — same values
print(a is b)   # False — different objects

# ✅ Use == for value comparison
if user_input == "yes":
    proceed()

# ✅ Use is only for None, True, False
if result is None:
    handle_none()

# ❌ Don't use is for strings or numbers (unreliable)
a = 1000
b = 1000
print(a is b)  # May be True or False — implementation detail!

4. Off-by-One Errors

# ❌ Missing the last element
items = ["a", "b", "c", "d", "e"]
for i in range(len(items) - 1):  # Stops at index 3, misses "e"
    print(items[i])

# ✅ FIX
for i in range(len(items)):  # Goes through all indices
    print(items[i])

# ✅ Even better — iterate directly
for item in items:
    print(item)

# ❌ Common range mistake
for i in range(1, 5):
    print(i)  # Prints 1, 2, 3, 4 — NOT 5!

5. Scope / UnboundLocalError

# ❌ BUG: Assignment creates a new local variable
count = 0

def increment():
    count = count + 1  # UnboundLocalError!
    return count

# ✅ FIX: Use global (sparingly)
count = 0

def increment():
    global count
    count += 1
    return count

# ✅ Better: Pass and return
def increment(count):
    return count + 1

count = 0
count = increment(count)

6. Forgetting to Return

# ❌ BUG: Function returns None
def add(a, b):
    result = a + b
    # Forgot return!

total = add(3, 4)
print(total)  # None

# ✅ FIX
def add(a, b):
    return a + b

total = add(3, 4)
print(total)  # 7

7. String Immutability

# ❌ Strings can't be modified in place
name = "hello"
name[0] = "H"  # TypeError!

# ✅ Create a new string
name = "hello"
name = "H" + name[1:]
print(name)  # Hello

# ✅ Or use replace()
name = "hello"
name = name.replace("h", "H")
print(name)  # Hello

8. Integer Division Surprise

# In Python 3, / always returns float
print(10 / 3)   # 3.3333...
print(10 / 2)   # 5.0 (not 5!)

# Use // for integer division
print(10 // 3)  # 3
print(10 // 2)  # 5

# ❌ Gotcha with negative numbers
print(-7 // 2)  # -4 (not -3! — rounds toward negative infinity)

9. Using = Instead of ==

# ❌ Assignment, not comparison (SyntaxError in if statement)
# if x = 5:  # SyntaxError in Python (thankfully!)

# But this silent bug can happen:
x = 5  # Assignment
x == 5  # Comparison (True), but result is unused

# ✅ Walrus operator (Python 3.8+) for assignment in expressions
if (n := len(items)) > 10:
    print(f"Too many items: {n}")

10. Forgetting self in Methods

# ❌ BUG: Missing self
class Dog:
    def __init__(name):  # Missing self!
        name = name

    def bark():  # Missing self!
        print("Woof!")

# ✅ FIX
class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        print(f"{self.name} says Woof!")

11. Dictionary Key Errors

# ❌ Crashes on missing key
user = {"name": "Alice", "age": 30}
print(user["email"])  # KeyError!

# ✅ Use .get() with default
email = user.get("email", "not provided")
print(email)  # not provided

# ✅ Or check first
if "email" in user:
    print(user["email"])

12. Not Closing Files

# ❌ File might stay open
f = open("data.txt", "r")
data = f.read()
# Forgot f.close()!

# ✅ Always use with
with open("data.txt", "r") as f:
    data = f.read()
# Automatically closed

13. Confusing append() and extend()

a = [1, 2, 3]

# append() adds ONE item (even if it's a list)
a.append([4, 5])
print(a)  # [1, 2, 3, [4, 5]]

# extend() adds EACH item from an iterable
a = [1, 2, 3]
a.extend([4, 5])
print(a)  # [1, 2, 3, 4, 5]

# += is the same as extend
a = [1, 2, 3]
a += [4, 5]
print(a)  # [1, 2, 3, 4, 5]

14. Chained Comparisons Surprise

# Python supports chained comparisons (this is a feature!)
x = 5
print(1 < x < 10)   # True  — same as (1 < x) and (x < 10)
print(1 < x > 3)    # True  — same as (1 < x) and (x > 3)

# ❌ But this can be confusing:
print(1 == True)     # True  (True equals 1)
print(0 == False)    # True  (False equals 0)
print("" == False)   # False (different types)

15. Import Circular Dependencies

# ❌ module_a.py imports module_b, and module_b imports module_a
# This causes ImportError or unexpected behavior

# module_a.py
# from module_b import func_b  # ❌ Circular!

# ✅ FIX: Restructure code, or import inside the function
def func_a():
    from module_b import func_b  # Import when needed
    return func_b()

Quick Reference Card

Mistake Fix
Mutable default args Use None, create new object in function
Modify list while iterating Use comprehension or iterate over copy
== vs is Use == for values, is only for None
Off-by-one Use range(len(x)) or iterate directly
UnboundLocalError Pass values as arguments, return results
Forgot return Always check functions return a value
String mutation Strings are immutable — create new ones
Missing self First parameter of methods is always self
KeyError Use .get() with default value
File not closed Always use with statement

Next up: Python Best Practices →

Updated: