The for i in range(len(...)) pattern is a classic Python construct that combines two fundamental tools: the for loop, the range() function, and the len() function to iterate over sequences by index. So this pattern is widely used for tasks that require both the index and the element of a list, string, or other iterable—or when you need to modify elements in place. In this article, we’ll break down exactly how for i in range(len(sequence)) works, why it’s still useful, and when you might want to consider alternative approaches.
Understanding the Core Components
Before diving into the pattern itself, let’s review the building blocks.
The for Loop in Python
A for loop allows you to iterate over any iterable (like a list, string, tuple, or dictionary). The basic syntax is:
for element in iterable:
# do something with element
For example:
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
This prints each fruit directly, without needing an index.
The range() Function
range() generates a sequence of numbers. The most common forms are:
range(stop)– numbers from 0 up to (but not including)stop.range(start, stop)– numbers fromstarttostop-1.range(start, stop, step)– numbers fromstarttostop-1, incrementing bystep.
For example:
for i in range(5):
print(i) # 0,1,2,3,4
The len() Function
len() returns the length (number of items) of a sequence. For a list of 5 items, len(my_list) returns 5.
The Classic Pattern: for i in range(len(sequence))
When you write:
for i in range(len(sequence)):
Python first evaluates len(sequence) to get an integer, then passes that integer to range(). Now, the result is a sequence of indices from 0 to len(sequence)-1. Inside the loop, you can access the element using sequence[i].
Why Use This Pattern?
There are several scenarios where this index-based approach is helpful:
-
You need to modify elements in place – Changing an item requires its index. Here's one way to look at it: doubling every number in a list.
-
You need both the index and the value – To give you an idea, when printing "index: value" pairs.
-
You need to access neighboring elements – Tasks like checking if a list is sorted or finding duplicates often require comparing
sequence[i]withsequence[i+1]Still holds up.. -
You’re working with parallel sequences – If you have two lists of the same length and need to pair elements by index,
range(len(list1))gives you a common index.
Step-by-Step Example
Let’s walk through a concrete example. Suppose we have a list of grades and we want to add 5 bonus points to each grade.
grades = [72, 85, 90, 68, 78]
for i in range(len(grades)):
grades[i] = grades[i] + 5
print(grades) # [77, 90, 95, 73, 83]
Explanation:
len(grades)returns 5.range(5)produces indices 0,1,2,3,4.- In each iteration,
itakes one of these index values. grades[i]accesses the current grade, adds 5, and assigns it back to the same position.
Common Patterns and Variations
1. Looping Backwards
To iterate from the last index to the first:
for i in range(len(my_list) - 1, -1, -1):
print(my_list[i])
This is useful when removing elements from a list while iterating (to avoid index shifting issues) Small thing, real impact..
2. Using enumerate() as an Alternative
Python’s built-in enumerate() is often a cleaner way to get both index and value:
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
This avoids calling len() and list indexing entirely. It’s more readable and Pythonic. That said, enumerate() cannot be used for in-place modification unless you also use the index — but for i, value in enumerate(lst): lst[i] = ...In real terms, works exactly like the range(len(... )) pattern It's one of those things that adds up..
Not obvious, but once you see it — you'll see it everywhere.
3. Using range(len()) with Strings
Strings are also sequences, so the same pattern works:
word = "hello"
for i in range(len(word)):
print(f"Character at index {i}: {word[i]}")
This can be used for tasks like creating a Caesar cipher or checking for palindromes Worth knowing..
When to Avoid for i in range(len(...))
While the pattern is valid, many Python developers consider it an anti-pattern in situations where direct iteration over elements is sufficient. For example:
- If you only need the elements, use
for item in sequence:. - If you need indices, use
enumerate(). - If you need to iterate over two sequences in parallel, use
zip().
The for i in range(len(...)) pattern can make code longer and less readable. It also requires an extra lookup (sequence[i]), which is slightly slower than direct iteration And it works..
Real-World Examples
Example 1: Finding the Maximum Element with Index
numbers = [10, 25, 7, 43, 19]
max_val = numbers[0]
max_idx = 0
for i in range(len(numbers)):
if numbers[i] > max_val:
max_val = numbers[i]
max_idx = i
print(f"Max value {max_val} at index {max_idx}")
Example 2: Reversing a List In-Place
def reverse_list(lst):
left, right = 0, len(lst) - 1
while left < right:
lst[left], lst[right] = lst[right], lst[left]
left += 1
right -= 1
(Notice that here we use a while loop instead of for i in range, because we need two moving indices.)
Example 3: Checking if a List is Sorted
def is_sorted(lst):
for i in range(len(lst) - 1):
if lst[i] > lst[i + 1]:
return False
return True
This is a clean use case because we need lst[i] and lst[i+1].
Under the Hood: How Python Executes for i in range(len(sequence))
When the Python interpreter sees this line:
- It evaluates
len(sequence)(calls the__len__method of the sequence object). - It passes that integer to
range(), which creates arangeobject. - The
rangeobject is iterable;forcalls__iter__()on it. - Each iteration, the
rangeobject yields the next integer. - The loop body uses
sequence[i]to access the element.
This is efficient because range objects are lazy: they don’t store all numbers in memory at once. So even for a list of 10 million items, range(len(big_list)) only stores start, stop, and step.
Frequently Asked Questions
Q: Is for i in range(len(list)) Pythonic?
A: It’s acceptable when you genuinely need the index for in-place modification or neighbor access. But many Pythonistas prefer enumerate or direct iteration for readability Which is the point..
Q: Can I use this pattern with tuples?
A: Yes, tuples are sequences, so for i in range(len(t)) works. Still, you cannot modify a tuple in place (tuples are immutable).
Q: What about custom objects?
A: If your custom class implements __len__ and __getitem__, the pattern will work. This is the foundation of Python’s sequence protocol Surprisingly effective..
Q: How does for i in range(len(list)) compare to for i, val in enumerate(list)?
A: enumerate is a generator that yields (index, value) tuples. It avoids an extra list[i] lookup, so it’s slightly more efficient and cleaner. Use enumerate unless you explicitly need to modify list[i] multiple times or access neighbors.
Q: What is a common mistake with this pattern?
A: Forgetting that range(len(x)) produces indices from 0 to len(x)-1. If you try to access x[i+1] when i is the last index, you’ll get an IndexError Simple, but easy to overlook. Practical, not theoretical..
Conclusion
The for i in range(len(sequence)) pattern is a powerful and flexible tool in Python. It gives you explicit control over indices, which is essential for in-place modification, neighbor comparisons, and parallel iteration. At the same time, modern Python offers alternatives like enumerate() and zip() that often lead to cleaner code. Knowing when to use each approach is a mark of an experienced Python programmer That alone is useful..
Worth pausing on this one Simple, but easy to overlook..
Key takeaways:
- Use
for i in range(len(...))when you need to modify elements by index or access adjacent elements. - Prefer
enumerate()when you need both index and value, but not modification. - Prefer
for item in iterablewhen you only need values. - Always consider readability: the most Pythonic solution is the one that communicates your intent clearly.