💡 TL;DR
Most "why is my Python broken" moments trace back to 8 repeat offenders: bad indentation, is vs ==, mutable default args, mutating a list while looping over it, closures inside loops, string concat in a loop, missing colons, and comparing floats directly. None of these mean you're bad at Python — they mean Python did exactly what you told it to, not what you meant.
It's 11 PM, your script worked five minutes ago, and now it's throwing something that makes zero sense. You didn't touch that line. You definitely didn't touch that function.
Here's the reassuring part: you're not bad at this. Most beginner bugs aren't about misunderstanding Python's syntax — they're about Python quietly doing exactly what the code says, not what you assumed it meant.
Two categories exist here. Syntax errors get caught before your code even runs. Silent logic errors run fine and just return the wrong answer — no red text, no stack trace, nothing pointing you to the problem. The second kind is the one that eats your evening.
The 8 Bugs
1. Mixing Tabs and Spaces
Python uses indentation instead of curly braces to define blocks. A tab and 4 spaces can look identical in your editor but mean something completely different to the interpreter.
# ❌ mixes a tab with spaces
def greet(name):
print("Hello,") # tab
print(name) # spaces
That throws IndentationError: inconsistent use of tabs and spaces. Fix: pick one, standardize, never mix.
# ✅ consistent
def greet(name):
print("Hello,")
print(name)
Set your editor to insert spaces on Tab and this bug disappears forever.
2. is Instead of ==
== checks value equality. is checks identity — whether two variables point to the same object in memory. These accidentally behave the same for small cached integers and short strings, which is exactly what makes the bug sneaky.
# ❌ works by accident for small numbers, breaks for larger ones
a = 1000
b = 1000
if a is b:
print("Same value") # False, not printed
# ✅ use == for value comparison
if a == b:
print("Same value") # True
One legit exception: always use is None, never == None.
3. Mutable Default Arguments
Default argument values get evaluated once, at function definition time — not on every call.
# ❌ same list reused across every call
def add_task(task, task_list=[]):
task_list.append(task)
return task_list
print(add_task("write code")) # ['write code']
print(add_task("review PR")) # ['write code', 'review PR'] — surprise!
# ✅ use None as sentinel
def add_task(task, task_list=None):
if task_list is None:
task_list = []
task_list.append(task)
return task_list
This one catches devs with months of Python experience, not just beginners.
4. Modifying a List While Looping Over It
Silent, no error, and genuinely dangerous in production data pipelines.
# ❌ indices shift as items get removed, results are unreliable
nums = [1, 2, 3, 4, 5, 6]
for n in nums:
if n % 2 == 0:
nums.remove(n)
Every .remove() shrinks the list and shifts everything after it left by one — but the loop's cursor has no idea that happened.
# ✅ build a new list instead of mutating during iteration
nums = [1, 2, 3, 4, 5, 6]
nums = [n for n in nums if n % 2 != 0]
I've seen a version of this quietly drop records from a data-cleaning script for months before anyone noticed the counts were off.
5. Closures Inside Loops
If you've written JavaScript, this one bites differently in Python — loop variables don't get a fresh scope per iteration, so every closure ends up referencing the same variable.
# ❌ all three print 2, not 0, 1, 2
functions = []
for i in range(3):
functions.append(lambda: print(i))
for f in functions:
f()
By the time you call these, the loop already finished and i holds its final value.
# ✅ capture the current value as a default arg
functions = []
for i in range(3):
functions.append(lambda i=i: print(i))
6. String Concatenation Inside a Loop
Strings are immutable in Python, so every += allocates a brand-new string. Fine at small scale, brutal at large scale.
# ❌ works, but gets painfully slow on big inputs
result = ""
for char in "some very long text" * 10000:
result += char
# ✅ collect in a list, join once
pieces = []
for char in "some very long text" * 10000:
pieces.append(char)
result = "".join(pieces)
Not a crash — a performance bug. Your "working" script suddenly takes 40 seconds instead of half a second, and nothing in the code "looks" wrong.
7. Forgetting the Colon
The classic first SyntaxError almost everyone hits.
# ❌
score = 85
if score > 80
print("Great job")
# ✅
if score > 80:
print("Great job")
Becomes muscle memory fast, but worth double-checking on if, for, while, def, and class lines until it does.
8. Comparing Floats Directly
Floating-point numbers are binary approximations, and not every decimal maps cleanly.
print(0.1 + 0.2 == 0.3) # False
0.1 + 0.2 actually evaluates to 0.30000000000000004. This is standard IEEE 754 behavior, not a Python bug.
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True
Syntax Error vs Silent Bug — Quick Reference
| Type | Example | Crashes? | How you find it |
|---|---|---|---|
| Syntax error | Missing colon, bad indentation | Yes, immediately | Error message points to the line |
| Silent logic bug | Mutable default arg, list mutation while looping | No | Wrong output, needs manual debugging |
| Performance bug | String concat in a loop | No | Runs correctly but slowly |
Wrapping Up
None of these bugs mean you're bad at coding. They mean you're writing enough real code to run into Python's actual behavior — which is exactly how you learn a language properly. Every single one boils down to the same root cause: Python did precisely what the code said, not what you assumed it meant.
Next time your script hands you a result that "shouldn't be possible," run it against this list before you start doubting your entire understanding of the language.
Found this helpful? Check out more at codepractice.in
Top comments (0)