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("Aiman") # → HiAiman print("Hi", end=" ") print("Aiman") # → Hi Aiman
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.
Try It Yourself
13 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.
Print 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.
Print 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.
Mini-Challenge · Batik Banner
8 minAisyah is designing a Hari Raya welcome banner for her class. She wants Python to print a simple batik 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 — "SELAMAT HARI 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 batik 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.
Homework
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 Aiman Lim). - 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.