Python for Loop

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.

Ninad
Ninad

A Python and PHP developer turned writer out of passion. Over the last 6+ years, he has written for brands including DigitalOcean, DreamHost, Hostinger, and many others. When not working, you'll find him tinkering with open-source projects, vibe coding, or on a mountain trail, completely disconnected from tech.

Articles: 18