Learning Goals
3 minBy the end of this lesson you can:
- Use
"*" * nto build a string of any length without typing each character. - Use
print(..., end="")to stop Python from jumping to a new line after every print. - Put one
forloop inside another to draw a 2-D shape — rows of columns.
Warm-Up
5 minLast lesson, for i in range(n): let us repeat a block exactly n times, no manual counter required. Today we'll let that same loop print a row, and a second loop pick the row.
Quick predict-the-output puzzle. What does this print?
for row in range(3): print("*" * 5)
Show the answer
***** ***** *****
The trick is "*" * 5 — Python turns it into "*****" in one go. The loop repeats that line three times. Two tools, one tiny picture.
"x" * n builds a string. print(end="") keeps the cursor on the same line. A for inside a for draws 2-D. That's the whole lesson — the rest is play.
New Concept · Three Tiny Tools
12 minTool 1 · String multiplication
Python lets you multiply a string by a whole number. The result is that string repeated that many times — no spaces, no commas, just stuck together.
print("*" * 7) # → ******* print("ab" * 3) # → ababab
Think of it like a stamp: "*" is the stamp, * 7 is how many times to press it. We'll use this to build whole rows in a single expression.
Tool 2 · print(..., end="")
By default, every print() jumps to a new line when it finishes. That's the invisible \n at the end. We can change it.
print("Hi", end="") print("Felix") # → HiFelix print("Hi", end=" ") print("Felix") # → Hi Felix
An empty string ("") means "no newline, no space — stick right against the next thing". A single space (" ") means "put one space between them". You choose.
To draw a row of characters one-by-one, we need to keep printing on the same line. Then, at the end of the row, a plain print() (with nothing inside) gives us the newline.
Tool 3 · Nested for loops
You can put a loop inside a loop. The outer loop steps through the rows; the inner loop steps through the columns of that row.
for row in range(3): for col in range(4): print("*", end="") print()
Reading this aloud: "For each of three rows: for each of four columns, print a star with no newline; then, after the inner loop, print a newline." The result:
**** **** ****
Two ways to make a 4 × 3 rectangle, then — print("*" * 4) three times, or the nested-loop version above. Both are correct. We'll need the nested shape the moment any column depends on which row we're on.
Worked Example · The Right Triangle
12 minPhase 1 · One row at a time, by hand
We want this shape — a right triangle that grows by one star per row, five rows tall:
* ** *** **** *****
Row 1 has 1 star, row 2 has 2 stars… row 5 has 5 stars. The number of stars on row r is simply r itself. That's the pattern — and it's the entire job of the loop.
Phase 2 · With "*" * r — the short way
String multiplication makes this almost too easy. Save as right_triangle.py:
Code
# right_triangle.py — a five-row right triangle height = 5 for row in range(1, height + 1): print("*" * row)
Three lines. range(1, height + 1) gives us 1, 2, 3, 4, 5 — start included, stop excluded, same half-open rule from last lesson. On row 1 we print "*" * 1, on row 2 we print "*" * 2, and so on.
Phase 3 · The same shape with a nested loop
Now the long way — same output, but with a loop drawing the row character-by-character. This is the form you'll need the moment a row is more than just "n stars in a row".
Code
# right_triangle_nested.py — same picture, two loops height = 5 for row in range(1, height + 1): for col in range(row): print("*", end="") print()
Read it carefully. The outer loop owns the row number. The inner loop owns the column number — and it runs row times, because we wrote range(row). So row 1 prints 1 star, row 2 prints 2 stars, … row 5 prints 5 stars. After the inner loop, the bare print() ends the line.
Output (both versions)
* ** *** **** *****
What changed?
Phase 2 is shorter — perfect when every row is just one character repeated. Phase 3 is more flexible — you can mix characters inside the inner loop (think a chessboard, where every other cell is dark). Same picture, different control. Pick the shape that matches the shape of the problem.
Basic
5 minThree timed challenges, twenty minutes total. Don't scroll to the hints until you've given each one a real try — even a wrong shape is great information.
Ask the user for a side length n. Print an n × n square of hash marks. For n = 4, the picture should be four rows of four # characters.
Hint
n = int(input("Side length? ")) for row in range(n): print("#" * n)
Every row is identical — n hashes long. No nested loop needed yet. Run it with n = 4 and n = 7 and watch the square grow.
Challenge 1
4 minPrint a triangle that starts wide and shrinks. Row 1 has 5 stars, row 2 has 4, … row 5 has 1. Use a for loop with a negative step from PY-L1-13.
Hint
for row in range(5, 0, -1): print("*" * row)
range(5, 0, -1) hands out 5, 4, 3, 2, 1. The stop value is 0 because we still want 1 to be printed — and range stops before the stop value.
Challenge 2
4 minPrint a five-row pyramid that's centred — like the right triangle, but every row is shifted right by the correct number of spaces so the tip sits in the middle.
* *** ***** ******* *********
Hint
height = 5 for row in range(1, height + 1): spaces = " " * (height - row) stars = "*" * (2 * row - 1) print(spaces + stars)
Two patterns going at once. The spaces shrink from 4 to 0. The stars grow from 1, 3, 5, 7, 9 — that's 2 × row − 1. Add them with + to build the line, then print it as one string.
Challenge 3 · Pattern Banner
8 minMia is designing a Birthday welcome banner for her class. She wants Python to print a simple pattern motif — a striped pattern of dots and crosses on a fixed-width banner.
Your file must:
- Ask the user for a banner width
width(useint()) and a banner heightheight. - For each row from
0toheight - 1:- If the row number is even (
row % 2 == 0) → print a row of dots:"." * width. - Otherwise → use a nested loop to print the row character-by-character, with a
"+"on every even column and a"."on every odd one.
- If the row number is even (
- The result for
width = 9,height = 5should look like:
......... +.+.+.+.+ ......... +.+.+.+.+ .........
Stretch goal. Above the banner, print a centred title — "GOOD MORNING RAYA" — surrounded by a line of = the full banner width. Count the title's length with len() (from PY-L1-05) to work out how many spaces to put in front of it.
Show one possible solution
# batik_banner.py — striped pattern motif width = int(input("Banner width? ")) height = int(input("Banner height? ")) for row in range(height): if row % 2 == 0: print("." * width) else: for col in range(width): if col % 2 == 0: print("+", end="") else: print(".", end="") print()
The non-negotiable parts are the outer for row in range(height), the row % 2 == 0 check, the inner for col in range(width) with its own col % 2 == 0 check, the end="" on the inner prints, and the bare print() that closes the row. Your spacing or symbols can vary.
Recap
3 minNested loops turn one-dimensional repetition into two-dimensional pictures. Pair them with "x" * n and print(end="") and you can draw any rectangle, triangle, or motif you can describe row-by-row. The hard part is no longer the syntax — it's spotting the pattern.
Vocabulary Card
- nested loop
- A loop inside another loop. The inner loop runs all the way through on every pass of the outer loop.
- string multiplication
"x" * n— gives you a new string that is"x"repeatedntimes, stuck together.- end="…"
- An optional argument to
print()that replaces the automatic newline. Use""to keep the cursor on the same line. - ASCII art
- Pictures built from ordinary keyboard characters — the oldest way to draw on a screen.
Extra Mission
4 minCreate a new file called name_banner.py — a personalised ASCII banner of your initials.
Rules:
- Ask the user for their two initials (one letter each — for example,
"A"and"L"for Felix Hart). - Ask for a banner
size(useint()) — this is both the width and the height of each letter's block. - Print the first initial as a solid
size × sizesquare using the letter as the brick — for size4with letter"A", that's four rows of"AAAA". - Print one blank line as a separator.
- Print the second initial as a
size-tall right triangle (1 character on row 1, 2 on row 2, …) using the second letter as the brick. - Stretch. Frame the whole thing in a border — print a line of
=exactlysizewide above and below each block, so the banner looks like a printed stamp.
Bring name_banner.py next class — we'll start using lists in PY-L1-15.
Sample · name_banner.py
# name_banner.py — a two-letter ASCII banner first = input("First initial? ") second = input("Second initial? ") size = int(input("Banner size? ")) # Block 1 — solid square of the first letter print("=" * size) for row in range(size): print(first * size) print("=" * size) print() # blank separator # Block 2 — right triangle of the second letter print("=" * size) for row in range(1, size + 1): print(second * row) print("=" * size)
Your wording and prompts can vary. The non-negotiables are: a for loop printing first * size for the square, a second for row in range(1, size + 1) printing second * row for the triangle, and (if you tackled the stretch) the "=" * size borders. Don't worry about getting the spacing pixel-perfect — that comes when we meet f-strings in Level 2.