DEV Community

loading...
Cover image for Understanding loops

Understanding loops

James Ononiwu
full-stack web developer. interested in learning and documenting my journey by writing articles.
・7 min read

Loops are what makes programming such a powerful art. in fact without iteration a lot of problems coding solves today wouldn't have been possibly solved, it would have been a tedious task to write repetitive instructions for the number of times needed. this is why understanding what's happening under the hood when an iteration is going on is very important in making efficient use of loops to become a better programmer.

The simple part

simple use of a loop is the use of a single loop to print values contained in a list, or print characters that make up a string etc.

#print values in a list
countries = ['Germany','USA','Spain']
for country in countries: #iterate over countries list
    print(country)

#print characters that make up a string
country = 'Germany'
for letter in country: #iterate over countries list
    print(letter)
Enter fullscreen mode Exit fullscreen mode

the above code is for simple cases and easy for a beginner to pick up.

Challenge most beginners face.

the real challenge comes when we have nested loops - that is a loop inside another loop. unless you are building a time machine, two nested loops are enough to solve most iteration problems. the number of nested loops is directly proportional to the efficiency of the code as the deeper the nesting the less efficient and more time required to run. let's combine the two examples above to form a nested loop.

#nested for loops
countries = ['Germany','USA','Spain']
for country in countries: #iterate over countries list
    print('beginning of outer loop') 
    print('{} is at index {} of {}'.format(country,countries.index(country), countries),'\n')
    print('beginning of inner loop for {}'.format(country))

    for letter in country: #iterate over each country in countries list
        print('{} is at index {} of {}'.format(letter,country.index(letter),country))
    print('end of inner loop for {}'.format(country),'\n')

print('end of outer loop')
Enter fullscreen mode Exit fullscreen mode

running the code above will give the output below.

beginning of outer loop
Germany is at index 0 of ['Germany', 'USA', 'Spain'] 

beginning of inner loop for Germany
G is at index 0 of Germany
e is at index 1 of Germany
r is at index 2 of Germany
m is at index 3 of Germany
a is at index 4 of Germany
n is at index 5 of Germany
y is at index 6 of Germany
end of inner loop for Germany 

beginning of outer loop
USA is at index 1 of ['Germany', 'USA', 'Spain'] 

beginning of inner loop for USA
U is at index 0 of USA
S is at index 1 of USA
A is at index 2 of USA
end of inner loop for USA 

beginning of outer loop
Spain is at index 2 of ['Germany', 'USA', 'Spain'] 

beginning of inner loop for Spain
S is at index 0 of Spain
p is at index 1 of Spain
a is at index 2 of Spain
i is at index 3 of Spain
n is at index 4 of Spain
end of inner loop for Spain 

end of outer loop
Enter fullscreen mode Exit fullscreen mode

The formating and explanation outputs are for a better understanding of what is going on.
the objective of the code is to print each country contained in the countries list with their index, and also print the characters that spell them. understanding loops have more to do with knowing how index changes with each iteration. from the example above each index range starts at 0 and ends at the length of the iterable -1, for instance, the count of values in countries list is 3, therefore iteration is of the range 0,1,2. for a nested loop, execution of the outer loop once, is followed by complete execution of the inner loop and then control is handed back to the outer loop to continue executing.


beginning of outer loop
Germany is at index 0 of ['Germany', 'USA', 'Spain'] 

beginning of inner loop for Germany
G is at index 0 of Germany
e is at index 1 of Germany
r is at index 2 of Germany
m is at index 3 of Germany
a is at index 4 of Germany
n is at index 5 of Germany
y is at index 6 of Germany
end of inner loop for Germany 

beginning of outer loop
USA is at index 1 of ['Germany', 'USA', 'Spain'] 
Enter fullscreen mode Exit fullscreen mode

this is continued until the outer loop iteration reaches the end of the list in this case at the value Spain being the last value in the list.
Let's see another example of a nested loop where we need to check for duplicates in a sequence of numbers.

 def is_unique(S):
    """Return True if there are no duplicate elements in sequence S."""
    for j in range(len(S)):
        for k in range(j+1, len(S)):
            if S[j] == S[k]:
                print('S[{}] {}, S[{}] {}'.format(j,S[j],k,S[k]),'duplicate found')
                return False #found duplicate
            else:
                print('S[{}] {}, S[{}] {}'.format(j,S[j],k,S[k]))
    return True #if we reach this, elements were unique
Enter fullscreen mode Exit fullscreen mode

Calling the function with the values..


result = is_unique([77,55,33,44,33])
print(result)
Enter fullscreen mode Exit fullscreen mode

we will get the output below

S[0] 77, S[1] 55
S[0] 77, S[2] 33
S[0] 77, S[3] 44
S[0] 77, S[4] 33
S[1] 55, S[2] 33
S[1] 55, S[3] 44
S[1] 55, S[4] 33
S[2] 33, S[3] 44
S[2] 33, S[4] 33 duplicate found

False
Enter fullscreen mode Exit fullscreen mode

Again, the output formatting is for you to better understand what is going on, but we did receive False showing that the list contains duplicate values which happen to be the number 33. as you can see from the output each iteration of the outer loop i.e S[0] is followed by a complete iteration of the inner loop.

S[0] 77, S[1] 55
S[0] 77, S[2] 33
S[0] 77, S[3] 44
S[0] 77, S[4] 33

S[1] 55, S[2] 33
S[1] 55, S[3] 44
S[1] 55, S[4] 33


S[2] 33, S[3] 44
S[2] 33, S[4] 33 duplicate found
Enter fullscreen mode Exit fullscreen mode

But there's something new in the code, and that is the comparison of values inside the loop using the current iteration index.

  for j in range(len(S)):
        for k in range(j+1, len(S)):
            if S[j] == S[k]:
Enter fullscreen mode Exit fullscreen mode

from the code above, you will understand that if the current index j of the outer loop is 0 then the first index k of the inner loop will be one because k starts at j+1, and that's why we have...

S[0] 77, S[1] 55

S[1] 55, S[2] 33

S[2] 33, S[3] 44
Enter fullscreen mode Exit fullscreen mode

which helped us to compare value at index j S[j] to value at index j+1 S[j+1] or Sk to see if they are same.

Index out of range

Consider the code below that print each country contained in a list of countries

countries = ['Germany','USA','Spain']
country_index = 0
while country_index < len(countries):
    print(countries[country_index])
    country_index += 1 #increment index
Enter fullscreen mode Exit fullscreen mode
Germany
USA
Spain
Enter fullscreen mode Exit fullscreen mode

Notice that using while loops we have to specify a counter (in this case country_index) which is used as a checkpoint to terminate the loop if the condition no longer holds true, else we will have the loop running indefinitely.

Now we want to print the values of the same list in reverse order by trying the code below.

countries = ['Germany','USA','Spain']
countries_len = len(countries)
while countries_len >= 0:
    print(countries[countries_len])
    countries_len -= 1 #decrement index
Enter fullscreen mode Exit fullscreen mode

after running the code, instead of getting what we want. we ended up with the output below.

 2 countries_len = len(countries)
      3 while countries_len >= 0:
----> 4     print(countries[countries_len])
      5     countries_len -= 1 #decrement index

IndexError: list index out of range
Enter fullscreen mode Exit fullscreen mode

list index out of range error.
What causes this error is simply the fact that indexing in python starts at 0. we might say we specified that by doing..

while countries_len >= 0:
Enter fullscreen mode Exit fullscreen mode

But that's not where the exception occurred. it happened at line 4.


----> 4     print(countries[countries_len])
Enter fullscreen mode Exit fullscreen mode

Because we are trying to get a value with an index that is greater than the maximum index of the list. len(countries) will give you the value 3 which is the count of elements in the list starting at 1, but indexing in python starts at 0. so the range of index of countries list is 0,1,2 having maximum value as 2.

print(countries_len)
Enter fullscreen mode Exit fullscreen mode

Gives the output.

3
Enter fullscreen mode Exit fullscreen mode

while printing the indexes


for i in range(len(countries)):
    print(i)
Enter fullscreen mode Exit fullscreen mode

gives.

0
1
2
Enter fullscreen mode Exit fullscreen mode

to correct the error we simply subtract 1 from the length of the list.

countries = ['Germany','USA','Spain']
countries_len = len(countries)-1 #subtract 1 from length
while countries_len >= 0:
    print(countries[countries_len])
    countries_len -= 1 #decrement index
Enter fullscreen mode Exit fullscreen mode

and we get the correct output.

Spain
USA
Germany
Enter fullscreen mode Exit fullscreen mode

Another example of this error is gotten when we re-write the previous is_unique function this way.

def is_unique(S):
    """Return True if ther are no duplicate elements in sequence S."""
    sorted_S = sorted(S) # sort the sequence S.
    for j in range(1, len(sorted_S)):
        if sorted_S[j] == sorted_S[j+1]:
            return False
    return True
Enter fullscreen mode Exit fullscreen mode

calling the function with the sequence below as argument..

numbers = [33,44,33,35,54,77]
result = is_unique(numbers)
print(result)
Enter fullscreen mode Exit fullscreen mode

We will get the error output.

4     for j in range(1, len(sorted_S)):
---------> 5         if sorted_S[j] == sorted_S[j+1]:
      6             return False
      7     return True

IndexError: list index out of range
Enter fullscreen mode Exit fullscreen mode

Because we are trying to access values from an index j+1 which will not exist if we reach the maximum index j of the list.

To correct this error we simply change j+1 to j-1, this will have the list compare it's current value S[j] with it's previous value S[j-1],

def is_unique(S):
    """Return True if ther are no duplicate elements in sequence S."""
    sorted_S = sorted(S)
    for j in range(1, len(sorted_S)):
        if sorted_S[j] == sorted_S[j-1]: # changed j+1 to j-1
            return False
    return True
Enter fullscreen mode Exit fullscreen mode

Executing

numbers = [33,44,33,35,54,77]
result = is_unique(numbers)
print(result)
Enter fullscreen mode Exit fullscreen mode

returned the correct output.

False
Enter fullscreen mode Exit fullscreen mode

we've seen what happens under the hood during an iteration, and also errors you might encounter and how to fix them. i hope this have helped you in understanding a loop better. Thanks for reading.

Discussion (6)

Collapse
waylonwalker profile image
Waylon Walker • Edited

I always advise against nesting loops at all cost. In real projects it quickly gets out of control and you end up 5+ tab stops out. I recommend treating items differently than their containers. Below I have refactored your first example using this methodology, keeping most of your code exactly the same.

# Un-nested for loops

def print_letters_from_countries(countries):
    "prints each letter for each country in a list of countries"
    for country in countries: #iterate over countries list
        print('beginning of outer loop') 
        print('{} is at index {} of {}'.format(country,countries.index(country), countries),'\n')
        print('beginning of inner loop for {}'.format(country))
        print_letters_from_country(country)

def print_letters_from_country(country):
    "prints each letter of a single country"
    for letter in country: #iterate over each country in countries list
        print('{} is at index {} of {}'.format(letter,country.index(letter),country))
    print('end of inner loop for {}'.format(country),'\n')


countries = ['Germany','USA','Spain']
print_letters_from_countries(countries)

Output

beginning of outer loop
Germany is at index 0 of ['Germany', 'USA', 'Spain']

beginning of inner loop for Germany
G is at index 0 of Germany
e is at index 1 of Germany
r is at index 2 of Germany
m is at index 3 of Germany
a is at index 4 of Germany
n is at index 5 of Germany
y is at index 6 of Germany
end of inner loop for Germany

beginning of outer loop
USA is at index 1 of ['Germany', 'USA', 'Spain']

beginning of inner loop for USA
U is at index 0 of USA
S is at index 1 of USA
A is at index 2 of USA
end of inner loop for USA

beginning of outer loop
Spain is at index 2 of ['Germany', 'USA', 'Spain']

beginning of inner loop for Spain
S is at index 0 of Spain
p is at index 1 of Spain
a is at index 2 of Spain
i is at index 3 of Spain
n is at index 4 of Spain
end of inner loop for Spain
Collapse
waylonwalker profile image
Waylon Walker

You can also get the index in a more pythonic fashion by using the enumerate function to get the index while looping. This version, uses enumerate and f-strings.

# Un-nested for loops

def print_letters_from_countries(countries):
    for i, country in enumerate(countries): #iterate over countries list
        print('beginning of outer loop') 
        print(f'{country} is at index {i} of {countries}\n')
        print(f'beginning of inner loop for {country}')
        print_letters_from_country(country)

def print_letters_from_country(country):
    for i, letter in enumerate(country): #iterate over each country in countries list
        print(f'{letter} is at index {i} of {country}')
    print(f'end of inner loop for {country}\n')


countries = ['Germany','USA','Spain']
print_letters_from_countries(countries)
Collapse
jamesbright profile image
James Ononiwu Author

nice update!

Collapse
jamesbright profile image
James Ononiwu Author

Thanks for the recommendation

Collapse
pentacular profile image
pentacular

To really understand loops it is important to understand that they are recursive. :)

Collapse
jamesbright profile image
James Ononiwu Author

that's a good point!