Python for Loop: Syntax, Iteration, and Performance
python for loop: Understand Python's for loop syntax, iteration patterns, range, enumerate, and performance considerations for efficient code.
The Python for loop is a fundamental construct for iterating over iterable objects. Unlike many languages, it doesn't rely on an index variable by default; it iterates directly over the elements of a sequence or any iterable. This design simplifies code and reduces off-by-one errors, but it also introduces specific patterns and pitfalls that developers need to understand.
The Python for Loop Syntax and Semantics
The basic syntax of a Python for loop is straightforward:
for item in iterable: # do something with item
The loop assigns each element of the iterable to the variable item and executes the indented block. The loop terminates when the iterable is exhausted. This works with lists, tuples, strings, dictionaries, sets, generators, and any object that implements the iterator protocol.
A common misconception is that the loop variable is scoped to the loop. In Python, the loop variable remains in the enclosing scope after the loop ends. For example:
for i in range(3): pass print(i) # outputs 2
This behavior can be useful but also surprising. If you need to know whether a loop completed, you can use the else clause, discussed later.
Iterating Over Sequences Directly
When you need to process each element of a list or tuple, iterate directly over the sequence rather than using an index. This is both more readable and faster because it avoids the overhead of indexing.
names = ["alice", "bob", "carol"] for name in names: print(name.title())
Direct iteration works for any iterable, including dictionaries. By default, iterating over a dictionary yields its keys:
config = {"host": "localhost", "port": 8080} for key in config: print(key, config[key])
If you need both key and value, use the .items() method. For sets, iteration order is arbitrary but consistent within a single run.
Using range() for Numeric Iteration
The range() function is the standard way to iterate a fixed number of times or generate a sequence of numbers. It returns a lazy sequence, so it doesn't allocate a list of all values in memory.
for i in range(5): print(i)
range() accepts up to three arguments: start, stop, and step. The stop value is exclusive. Common patterns include:
for i in range(2, 10, 2): print(i) # 2, 4, 6, 8
When you need an index while iterating over a sequence, range() combined with len() is tempting but often less readable than enumerate(). Use range() when you genuinely need numeric progression, not just an index.
Enumerate and Zip for Indexed and Parallel Iteration
enumerate() adds a counter to an iterable, returning tuples of (index, element). This is the idiomatic way to track the position of an item.
colors = ["red", "green", "blue"] for idx, color in enumerate(colors): print(f"{idx}: {color}")
You can specify a starting index with the start parameter. zip() pairs elements from multiple iterables, stopping at the shortest one:
names = ["alice", "bob"] scores = [85, 92] for name, score in zip(names, scores): print(name, score)
Both functions return iterators, so they are memory-efficient even for large inputs. In Python 3.10 and later, zip() accepts a strict flag to raise an error if lengths differ, which is useful for detecting data mismatches.
Controlling Loop Flow with break, continue, and else
The break statement exits the loop immediately, while continue skips the rest of the current iteration and moves to the next one. The else clause is less known: it runs only if the loop completes without hitting a break.
for n in range(2, 10): for x in range(2, n): if n % x == 0: print(n, "equals", x, "*", n // x) break else: print(n, "is prime")
The else clause is useful for search loops where you need to know whether a match was found. It avoids a separate flag variable. However, it can be confusing if you're not familiar with it, so use it sparingly and document its behavior.
Performance Considerations in for Loops
Python's for loop is generally slower than a while loop in some languages, but the difference is rarely the loop itself. The bottleneck is usually the operations inside the loop. Still, a few patterns can affect performance.
- Iterating directly over a list is faster than using
range(len(...))and indexing. The latter performs a method call for each element lookup. - Avoid modifying a list while iterating over it. This can lead to skipped items or infinite loops. Instead, build a new list or iterate over a copy.
- Local variable lookups are faster than global ones. If you use a function or a frequently accessed attribute inside a loop, assign it to a local variable before the loop.
# Slower: repeated global lookup for item in items: process(item) # Faster: local binding process_func = process for item in items: process_func(item)
- List comprehensions are often faster than a for loop that builds a list. They run at C speed internally and avoid the overhead of repeated
append()calls.
squares = [x**2 for x in range(1000)]
For large data, consider whether you can use generator expressions to avoid materializing a full list. This reduces memory usage but doesn't necessarily improve speed.
Common Mistakes and Edge Cases
A classic mistake is modifying the list you're iterating over. Removing elements while iterating can cause items to be skipped because the list's indices shift. For example:
nums = [1, 2, 3, 4] for n in nums: if n % 2 == 0: nums.remove(n) # Result: [1, 3]? Actually [1, 3, 4] - the 4 is skipped.
Instead, build a new list with a comprehension or iterate over a copy.
Another edge case is the behavior of range() with negative steps. range(10, 0, -2) produces 10, 8, 6, 4, 2. Remember that stop is exclusive, so 0 is not included.
When iterating over a dictionary, do not add or remove keys during iteration. This raises a RuntimeError in CPython. If you need to filter a dictionary, create a new one.
Finally, be aware that for loops in Python are not infinite by nature; they always iterate over a finite iterable unless you use itertools.repeat or a custom infinite generator. If you need an infinite loop, while True is more explicit.
Understanding these patterns ensures that your Python for loops are not only correct but also efficient and maintainable.