# For loop using range vs iterables

By Martin McBride, 2022-09-15
Tags: for loop range
Categories: python language intermediate python

Suppose you wanted to loop over all the values in a list. Novice Python programmers will often write something like this:

k = ['red', 'green', 'blue']
for i in range(len(k)):
print(k[i])


In this code, we use the len to find the length of the list k. The length is 3, in this case.

The range function provides 3 values starting from zero, ie 0, 1, and 2. The loop executes 3 times, with the loop variable i taking value 0, then 1, then 2. The loop prints k[i] for each value.

This works fine but it isn't considered good code for reasons we will discuss later. Here is a much more Pythonic loop:

k = ['red', 'green', 'blue']
for s in k:
print(s)


This time we don't use a loop index variable i. We loop over the list k directly.

Here is how the for loop works:

Loop over each element in k, and for each element:
assign the element to the loop variable s
execute the body of the loop


### Why this is better

Looking at the two loops the second loop does not need the extra variable i. While that is obviously a good thing, simpler code is always better, that isn't the main benefit. Look at the for loop line:

for s in k:


This tells you exactly what the code is doing - it is looping over the list k. Compare this with the first case:

for i in range(3):


All this tells you is that the loop will run 3 times. You have to look at the code inside the loop to figure out that each pass through the loop is actually processing the next element of k. That isn't too hard to do in this case because the loop body only contains one line, but in a more complex loop, you might have to study the code to work out what the loop is doing.

A second important benefit is that the first loop will normally be faster. In the first example we are maintaining a variable i, and each time through the loop we need to index into the list using that variable. In the second example, there is no index variable, and the loop always just fetches the next element in the list. The second code simply does less, so it is bound to be faster.

A final benefit of this form is that it lets us iterate over lazy iterators. A lazy iterator is a type of iterator that produces its values one by one, as required. With a lazy iterable, all you can do is ask for the next value, you can't use square brackets to ask for element k[i], so the indexing technique simply doesn't work. In that case, you have to loop over the lazy iterator directly, using a loop counter isn't possible.

Looping over the list directly is, therefore, shorter, easier to understand, works in all cases, and is faster than using a loop counter variable. You would need a very good reason not to do it that way. In fact, it is rare to see loop counter variables in well-written Python code.

### Looping over iterables

A for loop can loop over any iterable item. This includes sequences such as lists, tuples or strings. Looping over a tuple is the same as looping over a list, but looping over a string is slightly different (but very useful):

s = 'hello'
for c in s:
print(c)


In this case, on each pass of the loop, c contains the next character of the string. So the first time through the loop will print 'h', the next time will print 'e', and so on.

Python doesn't have a special data type for a character. A character is represented by a string of length 1.

for can also be used with other types of iterables such as generators and list comprehensions. These objects don't work in quite the same way as a list.

A list has all its values available before the loop starts - they are just the values in the list. A generator, on the other hand, doesn't create values until it is asked for them.

This is called lazy iteration

### How does range work?

So, to go right back to the beginning, how does range work? range is a function, so you might simplistically think of it as a function that returns a list:

v = range(5)   # v = [0, 1, 2, 3, 4] ? No!


This is how range used to work in Python 2. But since Python 3, the range function returns a range object, which is a lazy iterable. It creates the sequence of values 0 to 4, one at a time, when the for loop requests them.

In summary, where possible, avoid using a loop counter variable, and loop over the sequence directly. This simplifies the code and makes the intent clear, and runs a bit faster too.