Skip to content

Nested loops and printed patterns

~15 min ยท prerequisite: Leaving a loop with break and continue

What you will be able to do

  • Put one loop inside another, and predict how many times the inner block runs.
  • Print rectangles and triangles with print, using end="" to stay on the same line and print() to start a new one.
  • Use nested loops to count pairs and to build a multiplication total.

The idea

A nested loop is a loop inside the block of another loop. The outer loop runs its block once for each of its values. Each time the outer block runs, the inner loop runs all of its rounds from start to finish. If the outer loop has 3 values and the inner loop has 4, the inner block runs 12 times in total, which is 3 times 4.

Indentation shows the nesting.

The end argument of print, from the output lesson in Part 1, controls what is printed after the values. With end="", the next print continues on the same line. A call to print() with no values prints only the ending, which moves to a new line. A break inside the inner loop leaves only the inner loop. The outer loop continues with its next value.

Worked example

for row in range(3):
    for col in range(4):
        print("*", end="")
    print()

Line by line, this is what Python does:

  1. The outer loop starts with row equal to 0.
  2. The inner loop runs with col equal to 0, 1, 2, and 3. Each run prints one * and leaves the cursor on the same line.
  3. After the inner loop finishes, print() moves to a new line.
  4. The outer loop moves to row equal to 1, and the inner loop prints four more stars, followed by another new line.
  5. The same happens for row equal to 2. The inner loop ran 3 times 4, which is 12 times.

The output is:

****
****
****

Try it

Each block checks its result with assert.

runs = 0
for row in range(3):
    for col in range(4):
        runs += 1
assert runs == 12

total = 0
for a in range(1, 4):
    for b in range(1, 4):
        total += a * b
assert total == 36
import io
from contextlib import redirect_stdout

buffer = io.StringIO()
with redirect_stdout(buffer):
    for row in range(1, 4):
        for col in range(row):
            print("*", end="")
        print()
assert buffer.getvalue() == "*\n**\n***\n"
pairs = 0
for x in range(3):
    for y in range(3):
        if y > x:
            break
        pairs += 1
assert pairs == 6

The second block captures the output in a StringIO buffer, the same way the Part 1 print lesson did, so the assert can check the exact text. The third block uses break in the inner loop only. For x equal to 0, the inner loop counts one pair and then breaks. For x equal to 1, it counts two pairs. For x equal to 2, it counts three pairs. The total is 6.

Common mistakes

  1. Printing each star on its own line. Leaving out end="" makes every print start a new line:

    for row in range(2):
        for col in range(3):
            print("*")
    

    The output has six lines, each with one star. Add end="" to the inner print, and add print() after the inner loop.

  2. Putting the newline call inside the inner loop. The following code prints a new line after every star:

    for row in range(2):
        for col in range(3):
            print("*", end="")
            print()
    

    The output has six lines, one star on each. Move print() out of the inner loop, so it is indented under the outer loop only.

  3. Indenting the inner loop as if it were outside. The following code prints one long line instead of a grid:

    for row in range(3):
        for col in range(4):
            print("*", end="")
    print()
    

    The output is ************, with twelve stars on one line, because the print() is not inside the outer loop. Check the indentation of each line.

  4. Expecting the inner loop to run only once. The following check fails:

    runs = 0
    for row in range(3):
        for col in range(4):
            runs += 1
    assert runs == 3
    
    AssertionError
    

    The inner loop runs fully for each of the three rounds of the outer loop, so runs is 12.

Practice

  1. Use two loops with range(4) for the rows and range(2) for the columns. Count the cells and assert that the count is 8.
  2. Use nested loops over range(3) for i and j. Add i + j to a total for every pair. Assert that the total is 18.
  3. Use nested loops over range(1, 7) for a and b. Count the pairs where a * b equals 12, and assert that the count is 4.
  4. Use nested loops to print a triangle of + signs with 4 rows: one + in the first row, two in the second, and so on. Capture the output with redirect_stdout and assert that it equals "+\n++\n+++\n++++\n".
  5. Stretch: count the pairs (i, j) with 1 <= i < j <= 10 whose sum is even. Assert that the count is 20. Hint: the inner loop can start at i + 1, using range(i + 1, 11).
Solution
  1. python count = 0 for row in range(4): for col in range(2): count += 1 assert count == 8

  2. python total = 0 for i in range(3): for j in range(3): total += i + j assert total == 18

  3. python count = 0 for a in range(1, 7): for b in range(1, 7): if a * b == 12: count += 1 assert count == 4

The pairs are (2, 6), (3, 4), (4, 3), and (6, 2).

  1. ```python import io from contextlib import redirect_stdout

buffer = io.StringIO() with redirect_stdout(buffer): for row in range(1, 5): for col in range(row): print("+", end="") print() assert buffer.getvalue() == "+\n++\n+++\n++++\n" ```

  1. python count = 0 for i in range(1, 11): for j in range(i + 1, 11): if (i + j) % 2 == 0: count += 1 assert count == 20

Pairs with both numbers odd, or both even, have an even sum. There are five odd and five even numbers from 1 to 10, and each group gives C(5, 2) = 10 pairs.

Recap

  • An inner loop runs completely for each round of the outer loop, so the total number of runs is the outer count multiplied by the inner count.
  • print("*", end="") stays on the same line, and print() starts a new line.
  • A break in the inner loop leaves only the inner loop.

Sources