Tracing a Loop

Published

2023-07-31

Tracing a loop

Oftentimes, we wish to understand the behavior of a loop that perhaps we did not write. One way to suss out a loop is to use a table to trace the execution of the loop. When we do this, patterns often emerge, and—in the case of a while loop—we understand better the termination criteria for the loop.

Here’s an example. Say you were asked to determine the value of the variable s after this loop has terminated:

s = 0
for n in range(1, 10):
    if n % 2:
        # n is odd; 1 is truthy
        s = s + 1 / n
    else:
        # n must be even; 0 is falsey
        s = s - 1 / n

Let’s make a table, and fill it out. The first row in the table will represent our starting point, subsequent rows will capture what goes on in the loop. In this table, we need to keep track of two things, n and s.

n s
0

Before we enter the loop, s has the value 0.

Now consider what values we’ll be iterating over. range(1, 10) will yield the values 1, 2, 3, 4, 5, 6, 7, 8 and 9. So let’s add these to our table (without calculating values for s yet).

n s
0
1 ?
2 ?
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?

Since there are no break or return statements, we know we’ll iterate over all these values of n.

Now let’s figure out what happens to s within the loop. At the first iteration, n will be 1, which is odd, so the if branch will execute. This will add 1 / n to s, so at the end of the first iteration, s will equal 1 (1 / 1). So we write that down in our table:

n s
0
1 1
2 ?
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?

Now for the next iteration. At the next iteration, n takes on the value 2. Which branch executes? Well, 2 is even, so the else branch will execute and 1/2 will be subtracted from s. Let’s not perform decimal expansion, so we can write:

n s
0
1 1
2 1 - 1/2
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?

Now for the next iteration. n takes on the value 3, 3 is odd, and so the if branch executes and we add 1/3 to s. Again, let’s not perform decimal expansion (not doing so will help us see the pattern that will emerge).

n s
0
1 1
2 1 - 1/2
3 1 - 1/2 + 1/3
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?

Now for the next iteration. n takes on the value 4, 4 is even, and so the else branch executes and we subtract 1/4 to s.

n s
0
1 1
2 1 - 1/2
3 1 - 1/2 + 1/3
4 1 - 1/2 + 1/3 - 1/4
5 ?
6 ?
7 ?
8 ?
9 ?

Do you see where this is heading yet? No? Let’s do a couple more iterations.

At the next iteration. n takes on the value 5, 5 is odd, and so the if branch executes and we add 1/5 to s. Then n takes on the value 6, 6 is even, and so the else branch executes and we subtract 1/6 to s.

n s
0
1 1
2 1 - 1/2
3 1 - 1/2 + 1/3
4 1 - 1/2 + 1/3 - 1/4
5 1 - 1/2 + 1/3 - 1/4 + 1/5
6 1 - 1/2 + 1/3 - 1/4 + 1/5 - 1/6
7 ?
8 ?
9 ?

At this point, it’s likely you see the pattern (if not, just work out two or three more iterations). This loop is calculating

1 - \frac{1}{2} + \frac{1}{3} - \frac{1}{4} + \frac{1}{5} - \frac{1}{6} + \frac{1}{7} - \frac{1}{8} + \frac{1}{9}

See? At each iteration, we’re checking to see if n is even or odd. If n is odd, we add 1 / n; if n is even, we subtract 1 / n.

We can write this more succinctly using summation notation. This loop calculates

s = \sum_{n = 1}^{n = 9} (-1)^{n-1}\frac{1}{n}.

You may ask yourself: What’s up with the (-1)^{n-1} term? That’s handling the alternating sign!

  • What’s (-1)^0? 1.

  • What’s (-1)^1? -1.

  • What’s (-1)^2? 1.

  • What’s (-1)^3? -1.

  • What’s (-1)^4? 1.

Another example: factorial

In mathematics, the factorial of a natural number, n is the product of all the natural numbers up to and including n. It is written with an exclamation point, for example,

6\,! = 1 \times 2 \times 3 \times 4 \times 5 \times 6.

Let’s trace a Python loop which calculates factorial.

n = 6

f = 1
for i in range(2, n + 1):
    f = f * i

What does this loop do? At each iteration, it multiplies f by i and makes this the new f.

i f
1
2 2
3 6
4 24
5 120
6 720

This calculates factorial, n! for some n. (Yes, there are easier ways.) Remember:

n\,! = \prod\limits_{i=1}^{i=n} i.

Another example: discrete population growth

birth_rate = 0.05
death_rate = 0.03
pop = 1000  # population
n = 4

for _ in range(n):
    pop = int(pop * (1 + birth_rate - death_rate))
_ p
1000
1 1020
2 1040
3 1060
4 1081

Here we don’t use the value of the loop index in our calculations, but we include it in our table just to keep track of which iteration we’re on. In this example, we multiply the old pop by (1 + birth_rate - death_rate) and make this the new pop at each iteration. This one’s a nuisance to work out by hand, but with a calculator it’s straightforward.

What is this calculating? This is calculating the size of a population which starts with 1000 individuals, and which has a birth rate of 5% and a death rate of 3%. This calculates the population after four time intervals (for example, years).

Being able to trace through a loop (or any portion of a program) is a useful skill for a programmer.

Original author: Clayton Cafiero < [given name] DOT [surname] AT uvm DOT edu >

No generative AI was used in producing this material. This was written the old-fashioned way.

This material is for free use under either the GNU Free Documentation License or the Creative Commons Attribution-ShareAlike 3.0 United States License (take your pick).