Learning Goals
3 minBy the end of this lesson you can:
- Write a recursive turtle function that calls itself twice — "tree recursion".
- Use
turtle's position-saving and heading-restoring to keep recursive draws clean. - Tune three knobs — branch length, angle, depth — to design different trees.
- Add randomness for natural-looking variation.
Warm-Up · A Tree is a Recursive Object
5 minLook at a tree. The trunk has two main branches. Each branch is itself a smaller tree — with two main branches of its own. And so on, until you hit a twig.
tree(length, depth):
if depth == 0: return ← base case: stop branching
draw a line of length
turn left
tree(length * 0.7, depth - 1) ← left subtree (smaller)
turn right (back to centre, then more)
tree(length * 0.7, depth - 1) ← right subtree (smaller)
turn back to centre
walk back to the startThat's the whole shape. depth shrinks; eventually hits 0; recursion ends.
Each recursive call draws a smaller version of the same thing. The trick is leaving the turtle exactly where you found it — so the parent can draw the next branch from the right position.
New Concept · Save and Restore
14 minThe shape
import turtle as t def tree(length, depth): if depth == 0: return # Draw the trunk segment t.forward(length) # Left branch t.left(30) tree(length * 0.7, depth - 1) t.right(30) # Right branch t.right(30) tree(length * 0.7, depth - 1) t.left(30) # Return to start of this segment t.backward(length) t.speed(0) t.left(90) # face up tree(100, 6) t.done()
The key discipline: each recursive call leaves the turtle exactly where it started. The two t.right/left pairs cancel any heading change. The final t.backward(length) undoes the trunk forward.
The recursion tree
tree(100, 6)
├── forward 100
├── tree(70, 5) ← LEFT subtree
│ ├── tree(49, 4)
│ │ ├── tree(34, 3)
│ │ │ └── ... → base case at depth 0
│ │ └── tree(34, 3)
│ └── tree(49, 4)
└── tree(70, 5) ← RIGHT subtree
├── tree(49, 4)
│ ├── ...
│ └── ...
└── tree(49, 4)Depth 6 = up to 2⁶ = 64 leaves. Each branch is a self-similar smaller tree.
Three knobs to play with
length starting trunk length bigger = bigger tree
depth how many levels to draw bigger = denser
angle branching angle 30° is "tree-like";
45° feels stiffer, 20° softer
factor length × factor each step 0.7 is natural; 0.5 = quick;
0.9 = thin and tallRandomness for nature
A pure mathematical tree looks too perfect. Real trees have variation. Add a tiny bit of randomness to length, angle, or both:
import random def tree(length, depth): if depth == 0: return t.forward(length) angle_l = 25 + random.randint(-5, 5) angle_r = 25 + random.randint(-5, 5) factor = 0.7 + random.uniform(-0.1, 0.1) t.left(angle_l); tree(length * factor, depth - 1); t.right(angle_l) t.right(angle_r); tree(length * factor, depth - 1); t.left(angle_r) t.backward(length)
Each branch picks its own angle and shrink factor. The tree looks weather-worn, asymmetric, alive.
Colour by depth
def tree(length, depth, max_depth): if depth == 0: return # Trunk dark; tips green progress = 1 - depth / max_depth # 0 at start, ~1 at tips if progress < 0.5: t.color((80 + int(progress * 100), 50, 20)) # brown else: t.color((40, int(progress * 180), 40)) # green ...
Compute colour from depth — gradient effect. Trunk dark; tips green. The colormode(255) from PY-L2-35 is needed first.
Worked Example · The Living Tree
12 minSave as tree.py:
# tree.py — recursive turtle tree with random variation + colour import turtle as t import random screen = t.Screen() screen.setup(width=900, height=700) screen.bgcolor("ivory") screen.colormode(255) t.speed(0) t.hideturtle() t.left(90) t.penup() t.goto(0, -300) t.pendown() t.pensize(3) def tree(length, depth, max_depth): if depth == 0 or length < 2: return # Pick colour by depth progress = 1 - depth / max_depth # 0 at start → 1 at tips if progress < 0.5: c = (90 + int(progress * 80), 60, 30) # browns else: c = (60, 120 + int(progress * 100), 50) # greens t.color(c) # Vary pen thickness too t.pensize(max(1, depth // 1)) t.forward(length) # Random angles angle_l = 25 + random.randint(-8, 8) angle_r = 25 + random.randint(-8, 8) factor = 0.7 + random.uniform(-0.1, 0.1) # Left branch t.left(angle_l) tree(length * factor, depth - 1, max_depth) t.right(angle_l) # Right branch t.right(angle_r) tree(length * factor, depth - 1, max_depth) t.left(angle_r) # Walk back along the trunk we drew t.backward(length) random.seed(42) tree(length=110, depth=9, max_depth=9) t.done()
What you'll see
A tree about 600 pixels tall. Trunk thick and brown at the base, branches narrower and greener up top. Slight asymmetry from the randomness — looks hand-drawn. Each new run with a different seed gives a different tree.
Notice the recursion eats 2⁹ = 512 branches at depth 9. t.speed(0) is essential — without it you'd watch the turtle for ten minutes. The t.backward(length) at the end of each call is the "leave the turtle where you found it" trick.
Try It Yourself
13 minRun the worked example with angle fixed at 45° instead of 25°. The tree spreads wider — almost like a bush.
Modify the tree to split into THREE branches at each step — left, straight, right. The tree fills the space differently.
Hint
def tree3(length, depth): if depth == 0: return t.forward(length) # Three branches t.left(30) tree3(length * 0.6, depth - 1) t.right(30) tree3(length * 0.6, depth - 1) # straight up t.right(30) tree3(length * 0.6, depth - 1) t.left(30) t.backward(length)
Cube blowup — 3⁶ = 729 branches at depth 6. Keep depth low for sanity.
Search "Pythagoras tree turtle". Build a version where each branch is a small square with two children attached at 45°.
Hint
Each call draws a square instead of a line, then recurses on the top two corners with shorter side lengths. The proof-of-concept is online and well-documented. This is a famous fractal that turns out to fit inside a 4 × 6 rectangle exactly.
Mini-Challenge · Forest of Three
8 minDraw three trees side by side, each with different seed (and therefore different shape). Use one tree function and call it three times.
Show one possible solution
# forest.py — three trees, three seeds import turtle as t import random screen = t.Screen() screen.setup(width=1200, height=700) screen.bgcolor("ivory") screen.colormode(255) t.speed(0) t.hideturtle() def tree(length, depth, max_depth): if depth == 0 or length < 2: return progress = 1 - depth / max_depth t.color((80 + int(progress * 100), 60, 30) if progress < 0.5 else (60, 120 + int(progress * 100), 50)) t.pensize(max(1, depth)) t.forward(length) angle = 22 + random.randint(-7, 7) factor = 0.7 + random.uniform(-0.1, 0.1) t.left(angle); tree(length * factor, depth - 1, max_depth); t.right(angle) t.right(angle); tree(length * factor, depth - 1, max_depth); t.left(angle) t.backward(length) def jump_to(x, y, heading=90): t.penup() t.goto(x, y) t.setheading(heading) t.pendown() for x, seed in zip([-400, 0, 400], [1, 7, 42]): random.seed(seed) jump_to(x, -300) tree(95, 8, 8) t.done()
Non-negotiables: one tree function called three times, each with a different seed, positioned with jump_to. Three uniquely-shaped trees from the same code.
Recap
3 minA recursive tree function: base case stops at depth 0; recursive case draws a trunk, turns left, recurses, turns back, turns right, recurses, turns back, walks back to start. The "leave the turtle where you found it" discipline is what makes the parent's next branch end up in the right place. Three knobs — length, depth, angle — plus randomness give you infinite variation. Tree recursion is the visual equivalent of the call stack.
Vocabulary Card
- tree recursion
- A recursion that branches — each call makes two (or more) recursive calls.
- self-similar
- Each part of the structure looks like a smaller copy of the whole.
- position discipline
- Every recursive call leaves the turtle in the same place & heading it started.
- seed
random.seed(n)makes the random sequence repeatable. Different seeds = different trees from same code.
Homework
4 minDesign your own custom tree by changing at least three things:
- The branching pattern — 2 branches, 3 branches, or asymmetric (one big, one small).
- The angle — try 15° (pine), 45° (oak), 60° (palm).
- The colour scheme — winter (white tips), autumn (orange), or your own.
Save the output as a screenshot. Bring three of your favourites to class.
Sample · winter-pine tree
def pine(length, depth, max_depth): if depth == 0 or length < 2: return progress = 1 - depth / max_depth # Brown trunk → snowy white tips if progress < 0.5: t.color((90, 60, 30)) else: whiteness = int(150 + progress * 100) t.color((whiteness, whiteness, whiteness + 5)) t.pensize(max(1, depth)) t.forward(length) angle = 15 # narrow — pine-like t.left(angle); pine(length * 0.75, depth - 1, max_depth); t.right(angle) t.right(angle); pine(length * 0.75, depth - 1, max_depth); t.left(angle) t.backward(length) t.bgcolor("midnightblue") pine(120, 10, 10)
Non-negotiables: at least three design changes from the worked example. Take screenshots; experiment freely. The point is to play with the parameters.