A loop inside a loop, traced step by step with Python Tutor โ outer loop controls rows, inner loop controls stars per row.
This topic covers nested for loops in Python, using the classic example of printing a right-angled triangle made of stars (*). It combines two learning tools:
The goal is to understand how a nested loop executes internally (which is often the confusing part), not just what code to write.
i) controls the number of rows.j) controls how many stars are printed in each row.print("*", end=" ") prints a star without moving to a new line (keeps printing on the same line, separated by a space).print("") after the inner loop finishes prints an empty string, which just moves the cursor to the next line โ this is what creates the new row.i equals i (row 1 โ 1 star, row 2 โ 2 stars, etc.), because the inner loop runs range(0, i).for i in range(1, 6):
for j in range(0, i):
print("*", end=" ")
print("")
| Line | What it does |
|---|---|
for i in range(1, 6): | Outer loop โ runs i from 1 to 5 (5 rows total, since range(1,6) excludes 6). |
for j in range(0, i): | Inner loop โ runs j from 0 up to i-1, so it loops i times for each row. |
print("*", end=" ") | Prints a star and a space, staying on the same line (end=" " overrides the default newline). |
print("") | After the inner loop completes, this moves to a new line to start the next row. |
Expected Output:
*
* *
* * *
* * * *
* * * * *
Note: Rowiprints exactlyistars โ this is the key relationship to remember for any triangle-pattern problem.
Python Tutor was used to visualize the code execution step by step. This is very useful for understanding how variable values change over time in a nested loop.
i and j.| Step | Line executing | i | j | Output so far |
|---|---|---|---|---|
| 1 | for i in range(1,6): (about to start) | โ | โ | (empty) |
| 2 | for i in range(1,6): just executed โ next is for j in range(0,i) | 1 | โ | (empty) |
| 3 | for j in range(0,i): executing | 1 | 0 (about to be assigned) | (empty) |
| ... | inner loop runs, printing stars | 1 | 0 | * |
| ... (many steps) | outer and inner loops continue | increasing | increasing | building up triangle |
| 46 (final) | Done running | 5 | 4 | Full triangle: * / * * / * * * / * * * * / * * * * * |
Key observations from the trace:
range(1,6) โ every loop check, assignment, and print statement counts as a step.j resets back to the start of its range every time the outer loop moves to a new i. This is the essence of "nested" loops โ the inner loop is a completely fresh loop on every outer iteration.i and j change in the Global frame panel while watching the Print output grow is the best way to build intuition for nested loops.๐ก Tip: If a nested loop pattern is confusing, paste the code into Python Tutor and step through it line-by-line. Watching the variable values update alongside the output makes the logic click much faster than just reading the code.
Instead of hardcoding range(1, 6), the number of rows can be taken from the user so the pattern size becomes flexible.
rows = int(input("Enter the number of rows"))
for i in range(1, rows + 1):
for j in range(0, i):
print("*", end=" ")
print("")
input("Enter the number of rows") โ prompts the user and returns their input as a string.int(...) โ converts that string into an integer, since range() requires integers.range(1, rows + 1) โ changed from a hardcoded 6 to rows + 1, so the loop runs exactly rows times (e.g., if rows = 10, it loops i from 1 to 10).Input:
Enter the number of rows: 10
Output:
*
* *
* * *
* * * *
* * * * *
* * * * * *
* * * * * * *
* * * * * * * *
* * * * * * * * *
* * * * * * * * * *
Note: This confirms the pattern rule holds for any input โ rowialways has exactlyistars, up torows.
Before writing the loop, the pattern to build was sketched out as a comment for planning:
# *
# **
# ***
# ****
# *****
This is a useful habit โ sketch the expected output first (as comments), then reverse engineer the loop logic (rows vs. stars-per-row) from that shape.
end=" " in print() keeps output on the same line.print("") (or print()) is the trick used to move to a new line after each row.range(1, i) type inner loops are common for increasing patterns โ the inner loop's upper bound is tied to the outer loop's current value (i), not a fixed number.input() results with int() (or float()) when they'll be used in numeric operations like range().To print a triangle of stars with n rows:
rows = int(input("Enter the number of rows"))
for i in range(1, rows + 1): # controls row number / stars in that row
for j in range(0, i): # runs i times for row i
print("*", end=" ") # stay on same line
print("") # move to next line after row is done
i โ i stars.i.i, j, and the output panel update in real time if the logic feels unclear.