Learning Goals
3 minBy the end of this lesson you can:
- Decompose a Sierpinski triangle recursively — three sub-triangles, leaving a centre hole.
- Use the midpoint of two points:
((x1+x2)/2, (y1+y2)/2). - Draw a filled triangle in turtle from three corner coordinates.
- Get a third recursive-art pattern in your toolkit — distinct from tree branching and line replacement.
Warm-Up · The Decomposition
5 minStart: a big triangle with corners A, B, C
(we won't fill the centre)
Step 1: Find midpoints AB, BC, CA
Three smaller triangles form:
A - AB - CA (top)
AB - B - BC (bottom-left)
CA - BC - C (bottom-right)
The CENTRE triangle (AB - BC - CA) is the hole — we skip it.
Step 2: Apply the same rule to each of the three smaller triangles.
At each level the holes get smaller and more numerous.
The total filled area shrinks by 3/4 each step.Each recursive call splits one triangle into three smaller triangles, never the middle. Like yesterday's tree but spatial — three children per call, no "trunk" segment.
New Concept · Sierpinski Recursion
14 minThe shape
def sierpinski(a, b, c, depth): if depth == 0: # Base case: draw the filled triangle draw_triangle(a, b, c) return # Midpoints ab = midpoint(a, b) bc = midpoint(b, c) ca = midpoint(c, a) # Recurse on the three corners — NOT the centre sierpinski(a, ab, ca, depth - 1) sierpinski(ab, b, bc, depth - 1) sierpinski(ca, bc, c, depth - 1)
Three recursive calls. The centre triangle never gets drawn — that's the "hole". At depth 0 we actually draw a filled triangle.
The midpoint helper
def midpoint(p1, p2): return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2)
Tuples for points. p1[0] is x; p1[1] is y. Average each component.
Drawing a triangle from corner coordinates
def draw_triangle(a, b, c): t.penup() t.goto(a) t.pendown() t.begin_fill() t.goto(b) t.goto(c) t.goto(a) t.end_fill()
Jump to each corner, fill the enclosed area. Same as PY-L2-35's fill pattern — just driven by absolute coordinates instead of forward/turn.
The count of triangles
Depth Triangles drawn 0 1 1 3 2 9 3 27 4 81 5 243 6 729
3ⁿ. Each level triples the count. Don't exceed depth 6-7 on a normal computer.
Three knobs
Three corners Where the triangle is in space Depth How many recursive splits Fill colour Per-depth gradient looks great
Worked Example · The Sierpinski
12 minSave as sierpinski.py:
# sierpinski.py — recursive Sierpinski triangle import turtle as t screen = t.Screen() screen.setup(width=900, height=800) screen.bgcolor("ivory") screen.colormode(255) t.speed(0) t.hideturtle() def midpoint(p1, p2): return ((p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2) def draw_triangle(a, b, c, colour): t.color(colour) t.penup(); t.goto(a); t.pendown() t.begin_fill() t.goto(b); t.goto(c); t.goto(a) t.end_fill() def sierpinski(a, b, c, depth, max_depth): if depth == 0: # Pick a colour by position — gradient over the big triangle # Use the y-coordinate to pick where in the gradient we are avg_y = (a[1] + b[1] + c[1]) / 3 ratio = (avg_y + 350) / 700 # 0 at bottom, 1 at top r = int(220 - ratio * 80) g = int(60 + ratio * 100) b_ = int(120 + ratio * 80) draw_triangle(a, b, c, (r, g, b_)) return ab = midpoint(a, b) bc = midpoint(b, c) ca = midpoint(c, a) sierpinski(a, ab, ca, depth - 1, max_depth) sierpinski(ab, b, bc, depth - 1, max_depth) sierpinski(ca, bc, c, depth - 1, max_depth) # Three corners of the outer triangle top = ( 0, 350) left = (-380, -300) right = ( 380, -300) sierpinski(top, left, right, depth=6, max_depth=6) t.done()
What you'll see
A large triangle filled with 3⁶ = 729 small triangles arranged in the Sierpinski pattern. Holes at every scale. Colour gradients from pink at the top to green-blue at the bottom — a tiny visual treat.
Read the diff
Three helpers (midpoint, draw_triangle, sierpinski) total ~20 lines. The recursion never draws the middle triangle of a split — that's the entire mechanism. The colour-by-position trick uses average y-coordinate to make the gradient.
Try It Yourself
13 minRun the example at depths 0, 1, 2, 3, 4, 5, 6. See how the pattern emerges. Note how depth 7 takes noticeably longer.
Skip the begin_fill/end_fill — just draw the triangle outline at depth 0. The Sierpinski emerges as an outline-only fractal.
Hint
def draw_triangle_outline(a, b, c, colour): t.color(colour) t.penup(); t.goto(a); t.pendown() t.goto(b); t.goto(c); t.goto(a) # no fill
Same fractal; different aesthetic. Lighter on the eye at high depths.
The square version. Take a square. Divide it into 9 equal cells. Recurse on the 8 outer cells, skip the centre. Builds the famous Sierpinski carpet.
Hint
def sierpinski_carpet(x, y, size, depth): """x, y = top-left corner.""" if depth == 0: draw_square(x, y, size, "black") return third = size / 3 # 9 cells; skip cell (1, 1) — the centre for row in range(3): for col in range(3): if row == 1 and col == 1: continue sierpinski_carpet(x + col * third, y - row * third, third, depth - 1)
3³ for the depth growth means 8 children per call instead of 3 — gets dense fast. Try depth 4.
Mini-Challenge · Chaos Game Sierpinski
8 minThe Sierpinski triangle has a famous "chaos game" construction:
- Pick any point inside a triangle.
- Pick a random corner.
- Move halfway to that corner. Plot the new position.
- Repeat thousands of times.
The Sierpinski pattern emerges. Build it with turtle.dot instead of full triangles. No recursion needed — that's the surprise.
Show one possible solution
# chaos_game.py — Sierpinski without recursion import turtle as t import random screen = t.Screen() screen.setup(900, 800) screen.bgcolor("black") screen.colormode(255) t.speed(0) t.hideturtle() t.penup() corners = [(0, 300), (-300, -250), (300, -250)] x, y = (0, 0) # any starting point for _ in range(20_000): cx, cy = random.choice(corners) x = (x + cx) / 2 y = (y + cy) / 2 t.goto(x, y) t.dot(1, (255, 220, 180)) t.done()
Non-negotiables: 20k iterations, dot per point, random corner each step, half-step toward it. The Sierpinski pattern emerges from pure chance. This is one of the most beautiful results in pop maths — the same shape from two completely different constructions.
Recap
3 minSierpinski is your third recursive-art pattern. Tree recursion (PY-L3-33) branches off a trunk. Line replacement (PY-L3-34) substitutes each line with a more complex line. Sierpinski splits a region into sub-regions and skips one. All three are tree-recursive — each call makes multiple recursive calls. Each grows exponentially with depth — pick the depth that's detailed but not slow. Chaos-game Sierpinski shows the same pattern can emerge from completely different rules — fractals have a surprising mathematical depth.
Vocabulary Card
- Sierpinski triangle
- A fractal: each triangle splits into three smaller triangles, with the centre left empty.
- midpoint
- The average of two points. Used to find subdivision corners.
- 3ⁿ growth
- Number of base-case triangles at depth n. Caps practical depth around 6-7.
- chaos game
- The non-recursive construction. Random walk halfway to a corner; the Sierpinski pattern emerges.
Homework
4 minBuild sierpinski_pyramid.py. The 2-D Sierpinski has a 3-D cousin: the Sierpinski tetrahedron. For 2-D, draw three Sierpinski triangles in a row — the same shape rotated 0°, 120°, and 240° — to make a "tri-foil" pattern.
Stretch. Build a Sierpinski carpet (the square version from the Try section) at depth 4. Submit both screenshots.
Sample · Sierpinski carpet
def draw_square(x, y, size, colour): t.color(colour) t.penup(); t.goto(x, y); t.pendown() t.begin_fill() for _ in range(4): t.forward(size); t.right(90) t.end_fill() def carpet(x, y, size, depth): if depth == 0: draw_square(x, y, size, "navy") return third = size / 3 for row in range(3): for col in range(3): if row == 1 and col == 1: continue # leave centre empty carpet(x + col * third, y - row * third, third, depth - 1) carpet(x=-300, y=300, size=600, depth=4) t.done()
Non-negotiables: 9-cell decomposition, skip the centre, recurse on the 8 outer cells. 8⁴ = 4096 squares at depth 4 — noticeable but not slow.