Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

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:

Mercury
Venus
Earth
Mars

The 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-1
for i in range(5):
print(i, end=" ")
# 0 1 2 3 4
# range(start, stop) — start to stop-1
for i in range(2, 7):
print(i, end=" ")
# 2 3 4 5 6
# range(start, stop, step) — with custom step
for i in range(0, 20, 5):
print(i, end=" ")
# 0 5 10 15
# Count down
for i in range(5, 0, -1):
print(i, end=" ")
# 5 4 3 2 1

Iterating Over Different Types

Strings, tuples, sets, and files are all iterable:

# Strings — character by character
for char in "Python":
print(char, end="-")
# P-y-t-h-o-n-
# Tuples
for 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 line
with 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 keys
for key in person:
print(key)
# name, age, city
# Values only
for 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: London

As 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 — messy
for i in range(len(menu)):
print(f"{i + 1}. {menu[i]}")
# With enumerate — clear
for index, item in enumerate(menu, start=1):
print(f"{index}. {item}")
# 1. Espresso
# 2. Latte
# 3. Cappuccino
# 4. Americano

enumerate(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): 76

zip() 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: Bob
find_user(users, 99) # No user with id 99

This 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 skipped
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers) # [1, 3, 5] — looks right, but 2 was skipped during iteration

Safe alternatives:

# Option 1: iterate over a copy
for 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 comprehension
matrix = [[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.