LambdaLabTM
Computer Science · Class 11 · Practice Programs
PracticeA loop in a loop⏱️ 20 min read

Triangle Pattern Programs

Every pattern question in every paper is the same two loops. The outer loop determines the number of rows; the inner loop actually forms each row. And there is a trick for getting both of them right off the picture alone, without having seen the pattern before — it is on this page, and it is worth more than any one triangle.

The lesson these programs practiseNested for Loops

1The recipe, once, for all of them

Key Takeaway
Two loops, two jobs, and they never swap. The outer loop decides how many rows the pattern has — one round of it is one row, and it prints nothing itself. The inner loop actually forms each row, adding the characters of that row one at a time. Say those two sentences to yourself before every pattern question you are ever set; almost every wrong answer is a line that has been given to the wrong one of them.

Every program on this page is this shape. Learn the shape and ten patterns become one program with two or three lines changed:

recipe.py
for i in range(1, n + 1):      <- OUTER: how many rows
    line = ''                  <- start this row empty
    for j in range(1, ? + 1):  <- INNER: forms one row, a character at a time
        line = line + ?        <- add one character to this row
    print(line)                <- the finished row goes to the screen

Only the two question marks ever change. The first is how many characters this row needs, and the second is which character. Answer those two and the program is written.

Note
Why build a string instead of printing each star? print() moves to a new line every time, so printing stars one at a time would give a vertical column, not a triangle. There is a way to tell print() not to move down, but it belongs to a later chapter — so the row is collected in line with + and printed once, which works everywhere and has the useful side effect of making the row a value you can look at.

2Watch a pattern form

A printed book can only show you the finished triangle, which is the one thing you can already see. What it cannot show is the order: the row is not drawn, it is grown, one character at a time, and nothing at all reaches the screen until print(line) runs at the end of the row.

Step through any of the ten below. Watch i pick the row, watch the inner counter fill it in, and watch the screen stay perfectly still until the row is complete.

🔺 Watch the pattern form

Pick a triangle, then step through it. The row is built in memory first — the screen does not change until print(line) runs.

The row rule · row i gets i stars
pattern.py
outer headerinner headerbody — the indented blockoutside
n = 5
for i in range(1, n + 1):
line = ''
for j in range(1, i + 1):
line = line + '*'
print(line)
the plan behind it
rowsi1 to n + 1, step 1one round per row
one rowj1 to i + 1, step 1one round per star in this row
outer i · how many rows
inner counter · forms the row
rows printed
0 / 5
line — the row being built in memory
— no row in progress —
line = ''
the screen
 

Nothing has run yet. The outer loop takes the rows one at a time; the inner loop fills a row in.

Key Takeaway
The screen does not change during a row. That is the beat students miss, and it explains a whole family of bugs at once. If print(line) is indented into the inner loop, you get a line per character — a growing staircase. If it is pulled out to the margin, you get one line at the very end, holding only the last row.

3The trick: fill in the two lines first

Here is the part that turns all of this into something you can do in an exam, with a pattern you have never seen. Before writing a single line of Python, write the two loops out as blanks and fill them in off the picture:

the-plan
i ->  _  to  _ , step  _      <- the OUTER loop: how many rows
j ->  _  to  _ , step  _      <- the INNER loop: forms one row
Key Takeaway
Fill the inner loop in first. It is decided by one row, which you can see. The outer loop is decided by how the rows differ from each other, which you can only work out once you know what a row is made of. Doing it the other way round is what makes patterns feel like guesswork.

Take this pattern, and work it exactly the way the blanks ask:

pattern
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1
Where does a row start? → j starts at 1

Look along one row, any row. Every one of them starts at the same value, 1. The row is made by the j loop, so the j loop starts at 1.

2
How does a row move? → step 1

Along the row, each value is one more than the one before it. So the j loop steps by 1.

3
Where does a row end? → i + 1

This one is not the same on every row — row 3 ends at 3, row 5 ends at 5 — so it must depend on i. Row i ends at i, and because range() leaves the stop out, the stop is written i + 1.

4
Now the endings, read downwards → i starts at 1

Stop looking along the rows and look down their endings: 1, 2, 3, 4, 5. The first one is 1, so i starts at 1.

5
How do the endings change? → step 1

Each ending is one more than the ending above it, so i steps by 1 as well.

6
Where do the endings stop? → 6, which is n + 1

The last row ends at 5, so i must reach 5 — and the stop is excluded, so the stop is 6. Written for any number of rows, that is n + 1.

The blanks are full, and they were never a guess:

the-plan
i ->  1  to  n + 1 , step  1      (with 5 rows: 1 to 6)
j ->  1  to  i + 1 , step  1

for i in range(1, n + 1):
    for j in range(1, i + 1):

Two lines of plan, and the program's two headers fall straight out of them. All that is left to decide is what to add to the row — here str(j) + ' ', and that is the only thing that separates this pattern from the star triangle.

4When the row runs the other way

The worked example above was the friendly case: the row started at the same place every time and counted upwards. Two things can be different, and papers use both. The row's fixed value may be its ending rather than its start, and the counting may go down rather than up. The plan copes with both, with one rule stated properly:

Key Takeaway
The stop is one step past the last value you want. Going up, one step past 5 is 6. Going down, one step past 1 is 0. That is the same rule as “add one because the stop is excluded” — said in a way that survives a negative step, where adding one is exactly the wrong move.
Key Takeaway
Whichever value is the same on every row is a plain number; the other one depends on i. In the triangle above, every row started at 1, so the start was the number and the ending carried the i. Turn the pattern round so that every row ends at 1, and the two swap: the stop is a plain 0 and it is the start that becomes i.

Between them those two choices make four patterns, and here they are — the same five numbers, arranged four ways. Read the middle column as the answer to which end is nailed down?

1 2 3 4 5
1 2 3 4
1 2 3
1 2
1
fixed · starts at 1, always
varies · the ending: 5, 4, 3, 2, 1
j → 1 to i + 1, step 1
5 4 3 2 1
4 3 2 1
3 2 1
2 1
1
fixed · ends at 1, always
varies · the start: 5, 4, 3, 2, 1
j → i to 0, step -1
5
5 4
5 4 3
5 4 3 2
5 4 3 2 1
fixed · starts at 5, always
varies · the ending: 5, 4, 3, 2, 1
j → n to i - 1, step -1
5
4 5
3 4 5
2 3 4 5
1 2 3 4 5
fixed · ends at 5, always
varies · the start: 5, 4, 3, 2, 1
j → i to n + 1, step 1

All four have the same outer loop, and it counts backwards: i → n to 0, step -1. That is not a coincidence, it is the plan doing its job — in every one of them the column that varies reads 5, 4, 3, 2, 1 downwards, and the outer loop is read off that column.

Watch Out
i is not always the row number. It is whatever the picture says varies. In the third and fourth patterns i is the value the row ends or starts with — the rows grow while i counts down. Insisting that i must be 1 for the first row is what makes these four feel impossible; letting it be what the column says makes all four fall out of the same two lines.

5The four programs

Every row starts at 1 and ends at i, and the rows get shorter. Straight off the plan:

rows_1_to_i.py
# every row starts at 1 and ends where the outer loop says

n = 5

for i in range(n, 0, -1):
    line = ''

    for j in range(1, i + 1):
        line = line + str(j) + ' '

    print(line)
Output
1 2 3 4 5
1 2 3 4
1 2 3
1 2
1

Now the ending is the fixed one. Every row finishes at 1 and counts down to get there, so the step is -1, the stop is 0 — one step past 1 — and it is the start that carries the i:

rows_i_to_1.py
# every row ends at 1, counting down to it

n = 5

for i in range(n, 0, -1):
    line = ''

    for j in range(i, 0, -1):
        line = line + str(j) + ' '

    print(line)
Output
5 4 3 2 1
4 3 2 1
3 2 1
2 1
1
Tip
Both loops say to 0, for two different reasons. The outer one because its last row is numbered 1; the inner one because its last value is 1. They agree here by accident, and comparing them is a good way to check you have understood which is which.

The third turns it upside down: the rows grow, every row starts at 5, and the ending is what moves. Take i to be that ending and the stop is one step past it, going down — i - 1:

rows_n_down_to_i.py
# every row starts at 5 and stops one lower each time

n = 5

for i in range(n, 0, -1):
    line = ''

    for j in range(n, i - 1, -1):
        line = line + str(j) + ' '

    print(line)
Output
5
5 4
5 4 3
5 4 3 2
5 4 3 2 1

And the fourth is that one mirrored: the rows grow, every row ends at 5, and the start is what moves. Counting up again, so the stop is n + 1:

rows_i_up_to_n.py
# every row ends at 5, starting one lower each time

n = 5

for i in range(n, 0, -1):
    line = ''

    for j in range(i, n + 1):
        line = line + str(j) + ' '

    print(line)
Output
5
4 5
3 4 5
2 3 4 5
1 2 3 4 5
Key Takeaway
Four patterns, four plans, one method. Nothing here was guessed and nothing was remembered: each program is two lines of plan turned into two for headers. That is the whole reason the trick is worth learning — you will not be shown these four in the exam, you will be shown a fifth.

All four are in both widgets on this page: step through the formation of any of them in Watch a pattern form, and fill their blanks in yourself below.

6Fill the blanks yourself

Seven patterns, and the method is the same on every one of them. Work through the blanks and read why each gets the value it does — the four from the section above are there, and so is the pyramid further down this page, which is the one that needs two inner loops, one for the spaces and one for the stars.

🧩 Fill in the blanks, then write the code

The row loop first, off one row. The loop that counts the rows last, off the endings.

blank 1 of 6
the pattern you were given
1
1 2
1 2 3 ← this row
1 2 3 4
1 2 3 4 5

Reading along one row — that is where the inner loop comes from.

icounts the rowsone round per row
i?to?, step?
jforms one rowone round per number in this row
j?to?, step?

Look along one row. Every row starts at the same value, 1 — and the row is made by the j loop, so j starts at 1.

Tip
Four of these patterns share the same two lines. The right triangle, the counting rows, the repeated number and Floyd's triangle all come out as i → 1 to n + 1, step 1 with j → 1 to i + 1, step 1. They differ only in what gets added to the row. That is worth knowing walking into an exam: most of the marks are in one plan you already have.
📋 The problem

Ask for a number of rows and print a triangle of stars, one star in the first row, two in the second, and so on.

Row by row, for n = 5
row (i)stars neededinner rangethe row
11range(1, 2)*
22range(1, 3)**
33range(1, 4)***
44range(1, 5)****
55range(1, 6)*****

The third column is the whole program: the inner loop stops at i + 1, so its length is decided by the row it is in.

star_triangle.py
# a right-angled triangle of stars, one row at a time

n = int(input('How many rows? '))

for i in range(1, n + 1):
    line = ''

    for j in range(1, i + 1):
        line = line + '*'

    print(line)
Output
How many rows? 5
*
**
***
****
*****
for i in range(1, n + 1):

The outer loop determines the number of rows: five values, five rows. Nothing is printed by this line — it only decides which row is being built.

line = ''

Inside the outer loop but outside the inner one, so every row starts empty. Move it above the outer loop and all 15 stars end up on one line.

for j in range(1, i + 1):

The inner loop actually forms the row: its stop is i + 1, so it runs i times, one round per star. That the stop depends on i is what makes the triangle a triangle instead of a rectangle.

line = line + '*'

One star joins the row. Eight spaces of indentation: it belongs to the inner loop, which belongs to the outer one.

print(line)

Four spaces — in the outer body, after the inner loop has finished. It runs once per row, which is exactly five times.

Watch Out
Three lines, three indentation levels, three different programs. line = '' above the outer loop gives one long line of stars. print(line) inside the inner loop gives a growing staircase — *, *, **, *, **, *** and so on. Neither is an error. Both are just a line at the wrong depth.
star_triangle.py

8Stars — the same triangle, upside down

📋 The problem

Print the triangle the other way up: five stars in the first row, one in the last.

inverted_triangle.py
# the same triangle upside down: the first row is the longest

n = int(input('How many rows? '))

for i in range(n, 0, -1):
    line = ''

    for j in range(1, i + 1):
        line = line + '*'

    print(line)
Output
How many rows? 5
*****
****
***
**
*

The inner loop was not touched. Row i still gets i stars — it is the outer loop that now counts down: range(n, 0, -1) hands out 5, 4, 3, 2, 1. The stop is still excluded, which is why it is 0 and not 1; write range(n, 1, -1) and the last row goes missing.

9Numbers instead of stars

📋 The problem

Print a triangle where each row counts from 1 up to the row number. Then change it so that each row repeats its own row number instead.

number_triangle.py
# each row counts from 1 up to the row number

n = int(input('How many rows? '))

for i in range(1, n + 1):
    line = ''

    for j in range(1, i + 1):
        line = line + str(j) + ' '

    print(line)
Output
How many rows? 5
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
same_number_triangle.py
# each row repeats its own row number

n = int(input('How many rows? '))

for i in range(1, n + 1):
    line = ''

    for j in range(1, i + 1):
        line = line + str(i) + ' '

    print(line)
Output
How many rows? 5
1
2 2
3 3 3
4 4 4 4
5 5 5 5 5
Key Takeaway
The two programs differ by one letter: str(j) against str(i). j changes on every character, so the row counts up. i is fixed for the whole row, so the row repeats. Whenever a pattern question shows numbers, the first thing to ask is does this change along the row, or only down the column?
Watch Out
str() is not optional. line is a string and j is a number, and line + j raises TypeError: can only concatenate str (not "int") to str. The + ' ' at the end is what keeps the digits apart — without it, row 4 would read 1234. It also means every row ends with a space you cannot see, which is harmless here and worth knowing about before it puzzles you somewhere else.

10Floyd's triangle

📋 The problem

Print a triangle where the numbers carry on counting from row to row: 1; then 2 3; then 4 5 6.

floyds_triangle.py
# Floyd's triangle: the counting carries on from row to row

n = int(input('How many rows? '))
value = 1

for i in range(1, n + 1):
    line = ''

    for j in range(1, i + 1):
        line = line + str(value) + ' '
        value = value + 1

    print(line)
Output
How many rows? 5
1
2 3
4 5 6
7 8 9 10
11 12 13 14 15

The counter that matters here is neither i nor j. It is value, and its position is the whole program: started above the outer loop, so it survives from row to row, and increased inside the inner loop, so it moves once per number printed. Start it inside the outer loop instead and every row begins at 1 again — which is pattern 3.

11The pyramid, and the spaces nobody sees

📋 The problem

Print a centred pyramid: one star on top, then three, then five, each row pushed across so the pyramid stands up straight.

This is the one that defeats people, and always for the same reason: they count the stars and forget that the spaces are characters too. Count both, and the rule appears:

Row by row, for n = 4
row (i)spaces (n − i)stars (2i − 1)the row
131···*
223··***
315·*****
407*******

The dots are spaces, drawn so you can count them. Each row is n − i spaces and then 2i − 1 stars — the stars go up in odd numbers, which is what keeps the middle one in the middle.

pyramid.py
# a pyramid: spaces first, then an odd number of stars

n = int(input('How many rows? '))

for i in range(1, n + 1):
    line = ''

    for space in range(1, n - i + 1):
        line = line + ' '

    for star in range(1, 2 * i):
        line = line + '*'

    print(line)
Output
How many rows? 4
   *
  ***
 *****
*******

Two inner loops, one after the other, both inside the outer body and neither inside the other. The first appends the spaces, the second the stars, and because they run in that order the row comes out with its padding on the left. They are separate loops because they count different numbers of rounds — n - i and 2 * i - 1.

Tip
Where did the −1 go? The star loop is range(1, 2 * i), not range(1, 2 * i - 1 + 1). They are the same thing, because 2i − 1 + 1 is 2i — and the stop is excluded, so range(1, 2 * i) really does run 2i − 1 times. Row 3: range(1, 6), which is five rounds.

There is a shorter way to write the same program. String replication builds a repeated string without a loop at all, and for a pattern this simple it is the neater answer:

pyramid_short.py
# the same pyramid, using string replication instead of the two inner loops

n = int(input('How many rows? '))

for i in range(1, n + 1):
    line = ' ' * (n - i) + '*' * (2 * i - 1)
    print(line)
Output
How many rows? 4
   *
  ***
 *****
*******
Watch Out
Know both, and read the question. The outputs are identical — we ran both. But a question that says “using nested loops” is asking for the first version, and the one-liner will lose the marks however right its answer is. The long version is also the one that keeps working when the row is not made of one repeated character.

12The whole method, on one card

The plan settles the two for headers. Two things are left over that it does not decide, and they are where the remaining marks go:

1
Fill the inner line off one row

Where does the row start, how does it move, where does it end? Whichever of the two ends is the same on every row is a plain number; the other one is what depends on i.

2
Fill the outer line off the column that varies

The starts, or the endings — whichever moved. Read its first value, its step and its last value straight down the picture. It is not always the row number, and it is quite often counting down.

3
Put the stop one step past the last value

Going up, one past 5 is 6. Going down, one past 1 is 0. Both loops get the same treatment, and this is the rule that survives a negative step, where adding one is exactly wrong.

4
Decide what gets added

A star, str(j), str(i), or a running value the plan knows nothing about. If it changes along the row it comes from the inner counter; if it is the same all row it comes from i; if it carries on across rows it is a third variable started above the outer loop.

5
Count the spaces as characters

A centred or right-aligned pattern has spaces on the left, and they are part of the row. They need a line of the plan to themselves — a second inner loop, run before the stars.

6
Check the formula on every row

Not just the first. 1, 2, 3 is i; 5, 4, 3 is n − i + 1; 1, 3, 5 is 2i − 1. All three agree on some row somewhere, which is exactly why one row is not a check.

Key Takeaway
How many rounds altogether? A triangle of 5 rows runs its inner body 1 + 2 + 3 + 4 + 5 = 15 times, not 5 × 5. When the inner loop's length depends on i you cannot multiply — you have to add the rows up. That is a favourite exam question, and the widget above counts them for you if you want to check.

13Recap

The outer loop decides how many rows

One round of it is one row, and it prints nothing itself. Its bounds come from reading the row endings down the column.

The inner loop forms each row

It adds the characters of one row, one at a time. Its bounds come from reading along a single row, and its stop is the only part that depends on i.

Fill the two lines before writing code

i → _ to _, step _ and j → _ to _, step _. Inner first, off one row; outer second, off the endings. Then the headers write themselves.

line = '' between the two loops

Inside the outer loop, outside the inner one. Higher and the rows join into one; lower and only the last character survives.

print(line) after the inner loop

In the outer body, so it runs once per row. Inside the inner loop it prints a growing staircase.

The stop is one step past the last value

Going up that is last + 1; going down it is last − 1. Said this way the rule holds for a backwards row, where adding one is the wrong move.

Whichever end is fixed is a plain number

If every row starts the same, the start is the number and the ending carries the i. If every row ends the same, they swap.

Spaces are characters

A centred or right-aligned pattern needs its spaces counted and appended, before the stars, in a loop of their own.

✍️ Now write these yourself
  1. 1

    Print a right-aligned triangle — spaces first, then i stars, so the vertical edge is on the right.

    Hint · The pyramid with its second loop changed: n - i spaces, then i stars instead of 2i − 1.

  2. 2

    Print a square of stars, n rows of n.

    Hint · The inner loop stops at n + 1 instead of i + 1. It no longer depends on the row, so every row is the same length.

  3. 3

    Print a number triangle that counts down: row 3 reads 3 2 1.

    Hint · The inner loop can count down too: range(i, 0, -1).

  4. 4

    Print an inverted pyramid — the widest row on top, narrowing to one star.

    Hint · Same two inner loops, with the outer loop counting down. Work the spaces out from the row number again; do not guess.

  5. 5

    Print a full diamond: a pyramid, and then an inverted pyramid under it with the middle row not repeated.

    Hint · Two outer loops one after the other, each with its own pair of inner loops. Start the second at n - 1 so the widest row is not printed twice.

Quick Check

In the star triangle, where must line = '' be written?

Quick Check

A row counts down and finishes at 1. What is the inner loop's stop?

Quick Check

Every row of a pattern ends at 5 and the starts go 5, 4, 3, 2, 1 down the page. Which part of the inner loop depends on i?

Quick Check

Using the trick, which loop do you fill in first, and why?

Quick Check

A pattern's rows hold 1, 3, 5, 7 stars. What is the inner loop?

Quick Check

How many times does the inner body run for a 5-row right triangle?