Python for Loops: Iterating Lists, Ranges, and Everything Else
Python’s for loop is not a counting loop. It is an iteration loop: it works by asking an object for its next item, one at a time, until the items run out. This design means for works with lists, strings, dictionaries, files, generators, database cursors — anything that knows how to hand out items one by one.
The Simplest Case
planets = ["Mercury", "Venus", "Earth", "Mars"]
for planet in planets: print(planet)Output:
MercuryVenusEarthMarsThe variable planet takes on each value in turn. You name it whatever makes sense for the data you’re looping over.
range(): Generating Number Sequences
When you need to repeat something a specific number of times or iterate over indices, range() generates a sequence of integers on demand (without storing them all in memory).
# range(stop) — 0 to stop-1for i in range(5): print(i, end=" ")# 0 1 2 3 4
# range(start, stop) — start to stop-1for i in range(2, 7): print(i, end=" ")# 2 3 4 5 6
# range(start, stop, step) — with custom stepfor i in range(0, 20, 5): print(i, end=" ")# 0 5 10 15
# Count downfor i in range(5, 0, -1): print(i, end=" ")# 5 4 3 2 1Iterating Over Different Types
Strings, tuples, sets, and files are all iterable:
# Strings — character by characterfor char in "Python": print(char, end="-")# P-y-t-h-o-n-
# Tuplesfor value in (10, 20, 30): print(value)
# Sets (unordered — order not guaranteed)unique_values = {1, 2, 3, 4}for v in unique_values: print(v)
# Files — line by linewith open("data.txt") as f: for line in f: print(line.strip())Iterating Over Dictionaries
Dictionaries have three iteration modes:
person = {"name": "Alice", "age": 30, "city": "London"}
# Default: iterates over keysfor key in person: print(key)# name, age, city
# Values onlyfor value in person.values(): print(value)# Alice, 30, London
# Key-value pairs (most common)for key, value in person.items(): print(f"{key}: {value}")# name: Alice# age: 30# city: LondonAs of Python 3.7, dictionaries maintain insertion order, so iteration is predictable.
enumerate(): Index and Value Together
When you need both the position and the value, enumerate() is cleaner than indexing manually.
menu = ["Espresso", "Latte", "Cappuccino", "Americano"]
# Without enumerate — messyfor i in range(len(menu)): print(f"{i + 1}. {menu[i]}")
# With enumerate — clearfor index, item in enumerate(menu, start=1): print(f"{index}. {item}")
# 1. Espresso# 2. Latte# 3. Cappuccino# 4. Americanoenumerate(iterable, start=0) takes an optional starting index. The default is 0, but start=1 is common for user-facing lists.
zip(): Iterating Multiple Sequences Together
names = ["Alice", "Bob", "Carol"]scores = [88, 94, 76]departments = ["Engineering", "Sales", "Marketing"]
for name, score, dept in zip(names, scores, departments): print(f"{name} ({dept}): {score}")
# Alice (Engineering): 88# Bob (Sales): 94# Carol (Marketing): 76zip() stops when the shortest iterable runs out. If you need to continue with the longest iterable, use itertools.zip_longest().
for/else: The Misunderstood Pattern
Python’s for loop has an optional else clause that runs only if the loop completed without hitting a break.
def find_user(users, target_id): for user in users: if user["id"] == target_id: print(f"Found: {user['name']}") break else: # Runs only if break was never hit print(f"No user with id {target_id}")
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]find_user(users, 2) # Found: Bobfind_user(users, 99) # No user with id 99This is cleaner than using a flag variable to track whether the loop found something.
Modifying a List During Iteration
This is one of the most common bugs with for loops:
numbers = [1, 2, 3, 4, 5, 6]
# Bug: modifying the list while iterating causes items to be skippedfor n in numbers: if n % 2 == 0: numbers.remove(n)
print(numbers) # [1, 3, 5] — looks right, but 2 was skipped during iterationSafe alternatives:
# Option 1: iterate over a copyfor n in numbers[:]: if n % 2 == 0: numbers.remove(n)
# Option 2: build a new list (preferred)numbers = [n for n in numbers if n % 2 != 0]
# Option 3: filter()numbers = list(filter(lambda n: n % 2 != 0, numbers))Building a new list is the clearest approach and has no hidden bugs.
Comprehensions: Compact Iteration
For simple transformations and filters, list comprehensions are idiomatic Python:
squares = [x ** 2 for x in range(10)]evens = [x for x in range(20) if x % 2 == 0]
# Nested comprehensionmatrix = [[row * col for col in range(1, 4)] for row in range(1, 4)]# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]Use comprehensions for straightforward cases. If the logic inside requires multiple statements or nested conditions, a regular for loop with a body is easier to read.
Common Mistakes
Using range(len(iterable)) when you don’t need the index. Write for item in iterable, not for i in range(len(iterable)): item = iterable[i].
Not unpacking tuples in the loop variable. When iterating over a list of tuples, unpack directly in the for statement: for x, y in points rather than for point in points: x, y = point.
Assuming zip() gives you all items when sequences have different lengths. It silently stops at the shortest one.