The Error
You're looping over something β a function result, a database row, an API response β and Python throws:
TypeError: 'NoneType' object is not iterable
Translation: you tried to iterate over None. Every iteration mechanism in Python β for loops, list comprehensions, tuple unpacking, * spread β needs an actual iterable. None isn't one.
Here's the simplest way to trigger it:
def get_users():
pass # forgot to return anything
for user in get_users(): # TypeError here
print(user)
Root Cause
Any Python function without an explicit return statement silently returns None. That's the language spec, not a bug. But several other patterns produce the same trap:
- A function returns
Noneon a branch you didn't think about β anifwith no matchingelse, for example - In-place methods like
list.sort()ordict.update()modify the object and returnNoneβ always - An ORM query, database cursor, or API call returns
Nonewhen the result set is empty - You assign the return value of one of those in-place methods to a variable
The last one trips up developers constantly. Here's what it looks like:
items = [3, 1, 2]
items = items.sort() # sort() returns None, not the sorted list!
for item in items: # TypeError: 'NoneType' object is not iterable
print(item)
items is now None. The original list is sorted β but you threw away the reference to it.
How to Fix It
Fix 1: Check what the function actually returns
Before looping, print or inspect the value:
result = get_users()
print(type(result), result) # Debug: see what you actually got
If the output is <class 'NoneType'> None, the function isn't returning what you expect. Find it and add a return statement:
def get_users():
users = db.query("SELECT * FROM users")
return users # was missing before
Fix 2: Guard with an explicit None check
Sometimes None is a valid outcome β no results found, nothing to process. Guard before iterating:
users = get_users()
if users is not None:
for user in users:
print(user)
Or collapse it into one line:
for user in (users or []):
print(user)
The or [] substitutes an empty list when users is None. The loop runs zero times and no exception is raised.
Fix 3: Default to an empty collection inside the function
Fixing every call site is tedious. Fix the function once instead:
def get_users():
result = db.query("SELECT * FROM users")
if result is None:
return [] # always return a list
return result
Callers can now iterate freely. No guards needed at every use site.
Fix 4: Fix the in-place method assignment trap
list.sort(), list.reverse(), dict.update(), and set.add() all modify in place. They return None. Don't assign their result:
# Wrong
items = items.sort()
# Correct: sort in place, keep the reference
items.sort()
# Or use sorted() β it returns a new list
items = sorted(items)
Fix 5: Handle None from chained calls
Method chaining hides None until it blows up. This is risky:
# response.json() might return None
for item in response.json().get("results"):
process(item)
Break it apart and check at each step:
data = response.json()
results = data.get("results") if data else []
for item in (results or []):
process(item)
Fix 6: Walrus operator (Python 3.8+)
When the function is external and you can't change its return type, the walrus operator is cleaner than a two-line check:
if (users := get_users()) is not None:
for user in users:
print(user)
Finding Where None Comes From
Read the full traceback. Python pinpoints the exact line:
Traceback (most recent call last):
File "app.py", line 12, in process_orders
for order in get_orders(user_id):
TypeError: 'NoneType' object is not iterable
Go to line 12. The iterable is get_orders(user_id). Add a debug print above it:
result = get_orders(user_id)
print(f"get_orders returned: {result!r}")
for order in result:
...
The !r format calls repr() on the value. It clearly distinguishes None from an empty string '' or empty list [] β all three look different in repr output.
Verification
Run the code again after your fix. No TypeError means it worked. To confirm the guard path handles None correctly, simulate it explicitly:
def get_users():
return None # simulate no results
users = get_users()
for user in (users or []):
print(user)
print("Done β no error")
Expected output:
Done β no error
The loop body is skipped. No exception. That's the behavior you want.
Prevention
- Add type hints:
def get_users() -> list[User]:documents the expected return type. Run mypy and it will flag any code path that returnsNoneinstead. - Be honest with
Optional: If a function legitimately can returnNone, annotate it asOptional[list]. That signals to callers: check before you iterate. - Never assign in-place method results:
sort(),reverse(),update(),append()β all returnNone. Tattoo this on your brain. - Return empty collections, not None: A function fetching a list should return
[]on an empty result, notNone. ReserveNonefor "this value doesn't exist" β not "I found nothing."

