for item in sequence:
print(item)
That’s a Python for loop in its most basic form. The for keyword, a variable name, the in keyword, something iterable, and a colon. Everything indented beneath executes once for each item.
fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
print(f"I have a {fruit}")
# Output:
# I have a apple
# I have a banana
# I have a cherry
Python for loops don’t work like C or Java. There’s no counter initialization, no condition check, no increment expression. Python took one look at for(int i=0; i<n; i++) and said “that’s unnecessarily complicated.” Instead, Python loops iterate directly over collections.
How Python for loops actually work under the hood
When you write a for loop, Python calls the __iter__() method on your sequence. This returns an iterator object. Then Python repeatedly calls __next__() on that iterator until it raises a StopIteration exception.
numbers = [1, 2, 3]
iterator = iter(numbers)
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
print(next(iterator)) # StopIteration exception
This matters because it means anything implementing the iterator protocol works in a for loop. Lists, tuples, strings, dictionaries, files, generators, custom objects. If it’s iterable, you can loop it.
Running Python for loops through different data structures
Strings iterate character by character:
for char in "Python":
print(char)
# P
# y
# t
# h
# o
# n
Dictionaries iterate over keys by default:
prices = {'apple': 0.5, 'banana': 0.3, 'cherry': 0.8}
for item in prices:
print(item)
# apple
# banana
# cherry
Want the values? Call .values(). Want both? Use .items() and unpack:
for item, price in prices.items():
print(f"{item} costs ${price}")
# apple costs $0.5
# banana costs $0.3
# cherry costs $0.8
That unpacking syntax is powerful. Any iterable that yields tuples can be unpacked directly in the loop declaration:
coordinates = [(0, 0), (1, 2), (3, 5)]
for x, y in coordinates:
print(f"Point at ({x}, {y})")
When you actually need an index with range()
Sometimes you need the index. The range() function generates sequences of numbers:
for i in range(5):
print(i)
# 0, 1, 2, 3, 4
Range takes up to three arguments: range(start, stop, step). The stop value is exclusive. Always.
for i in range(2, 10, 2):
print(i)
# 2, 4, 6, 8
Combining range with len() lets you index into sequences:
words = ['alpha', 'beta', 'gamma']
for i in range(len(words)):
print(f"Index {i}: {words[i]}")
But that’s ugly. Python has enumerate() specifically for this:
for index, word in enumerate(words):
print(f"Index {index}: {word}")
You can even set a custom starting index:
for index, word in enumerate(words, start=1):
print(f"{index}. {word}")
# 1. alpha
# 2. beta
# 3. gamma
Processing multiple sequences with zip()
Zip combines multiple iterables element-wise:
names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
cities = ['NYC', 'LA', 'Chicago']
for name, age, city in zip(names, ages, cities):
print(f"{name} is {age} and lives in {city}")
Zip stops when the shortest sequence ends. If you need all elements, use itertools.zip_longest().
Loop control with break and continue
Break exits the loop immediately:
for num in range(1, 100):
if num > 5:
break
print(num)
# 1, 2, 3, 4, 5
Continue skips to the next iteration:
for num in range(1, 6):
if num == 3:
continue
print(num)
# 1, 2, 4, 5
Python has a weird feature: for-else. The else block executes only if the loop completes without hitting a break:
for num in [2, 4, 6, 8]:
if num % 2 != 0:
print("Found odd number")
break
else:
print("All numbers are even")
# All numbers are even
This is useful for search operations. You break when you find something, and the else handles the “not found” case.
Nested loops and list comprehensions
Loops nest exactly how you’d expect:
for i in range(3):
for j in range(3):
print(f"({i}, {j})", end=" ")
print()
# (0, 0) (0, 1) (0, 2)
# (1, 0) (1, 1) (1, 2)
# (2, 0) (2, 1) (2, 2)
But nested loops often become list comprehensions:
matrix = [[i * j for j in range(3)] for i in range(3)]
# [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
List comprehensions are faster than equivalent for loops because they’re optimized at the bytecode level. They’re also more Pythonic when you’re building a new list from an existing iterable.
Performance considerations that actually matter
For loops in Python are slow compared to languages like C. Each iteration involves multiple function calls and attribute lookups. That overhead adds up.
When performance matters, move work out of the loop:
# Slow - attribute lookup every iteration
result = []
for i in range(10000):
result.append(i * 2)
# Faster - cache the method
result = []
append = result.append
for i in range(10000):
append(i * 2)
# Fastest - use list comprehension
result = [i * 2 for i in range(10000)]
Better yet, use NumPy for numeric operations. Vectorized operations skip Python’s loop overhead entirely:
import numpy as np
numbers = np.arange(10000)
result = numbers * 2 # All multiplication happens in C
The bottom line on Python for loops
Python for loops prioritize readability over raw speed. They work with any iterable object, support tuple unpacking, and integrate with tools like enumerate(), zip(), and range(). The syntax is clean, the behavior is predictable, and the integration with Python’s iterator protocol makes them surprisingly flexible.
You iterate over collections, not indices. You use comprehensions when building new sequences. You reach for NumPy when speed actually matters. That’s the Python way.

