Lesson 18: Turtle Graphics and Building a GUI
🎧 Listen to this lesson
Text in, text out has been the whole course so far. This lesson breaks into graphics: turtle, where a pen-carrying turtle executes your movement commands and draws — the friendliest possible way to *see* loops and functions work — and tkinter, Python's built-in GUI toolkit, where windows, buttons and labels respond to clicks. Both rest on an idea text programs hide: an event loop that keeps a window alive and reacts.
Turtle: programs you can watch
A turtle has a position, a heading and a pen. forward(n) moves it n pixels, left(a)/right(a) rotate it a degrees, penup()/pendown() lift and lower the pen, and turtle.done() keeps the window open until you close it. A square is a loop wearing its geometry on the outside — four sides, four 90° turns:
import turtle
pen = turtle.Turtle()
pen.speed(3)
for _ in range(4):
pen.forward(100)
pen.left(90)
turtle.done()
Run it and a square draws itself. The lesson inside the picture: the loop body runs four times and the turtle's state (position, heading) carries between iterations — the same loop mechanics as always, made visible. Change 90 to 89 and the square never quite closes; graphics punish imprecision instantly and visibly, which is precisely why they teach programming so well.
Worked example 1 — one function, any polygon
The square's secret generalises: a regular polygon with sides sides turns by the exterior angle 360 / sides each step. Parameterise it (Lesson 4) and the same function draws every regular shape:
import turtle
def draw_polygon(pen, sides, length):
angle = 360 / sides
for _ in range(sides):
pen.forward(length)
pen.left(angle)
pen = turtle.Turtle()
pen.color("darkgreen")
draw_polygon(pen, 5, 90)
pen.penup()
pen.goto(-150, -120)
pen.pendown()
draw_polygon(pen, 3, 120)
turtle.done()
A green pentagon, then the pen lifts, relocates, and draws a triangle — sides=5 and sides=3 flowing through identical code. Angles make a good second experiment: a five-pointed star is one continuous path with a 144° right turn at each point:
import turtle
pen = turtle.Turtle()
pen.color("darkorange")
for _ in range(5):
pen.forward(150)
pen.right(144)
turtle.done()
Why 144? The star's points demand turning by 180 − 36; deriving it rigorously is a lovely geometry exercise, and testing candidate angles on screen is the fastest way to believe the answer. This whole section is a sandbox — change numbers, break shapes, fix them. That loop of predict → run → compare is the scientific method at keyboard scale.
Worked example 2 — drawing data
Charts are just shapes whose sizes come from data. A bar chart maps each value to a height with one scaling decision: the biggest value gets the tallest allowed bar, everything else scales in proportion. Compute the geometry first, in plain text:
data = {"Mon": 4, "Tue": 6, "Wed": 5, "Thu": 8, "Fri": 7}
SCALE = 220 / max(data.values())
for day, value in data.items():
print(day, value * SCALE)
Output:
Mon 110.0
Tue 165.0
Wed 137.5
Thu 220.0
Fri 192.5
Thursday, the maximum, lands exactly on 220 and the rest follow proportionally (Monday's 4 is half of Thursday's 8, and 110 is half of 220 — the arithmetic is the chart's honesty guarantee). Drawing then repeats a rectangle at increasing x positions, filling each:
import turtle
def draw_bar(pen, height):
pen.begin_fill()
for _ in range(2):
pen.forward(40)
pen.left(90)
pen.forward(height)
pen.left(90)
pen.end_fill()
pen.penup()
pen.forward(60)
pen.pendown()
data = {"Mon": 4, "Tue": 6, "Wed": 5, "Thu": 8, "Fri": 7}
SCALE = 220 / max(data.values())
screen = turtle.Screen()
screen.setup(500, 400)
pen = turtle.Turtle()
pen.penup()
pen.goto(-180, -150)
pen.pendown()
pen.fillcolor("steelblue")
for value in data.values():
draw_bar(pen, value * SCALE)
turtle.done()
The rectangle path inside draw_bar — forward, turn, forward, turn, twice — traces width then height; begin_fill/end_fill flood the traced outline; and the penup hop of 60 (40 of bar + 20 of gap) marches the baseline. Separating the scaling computation from the drawing, and testing the numbers as plain text first, is the professional pattern: geometry bugs are visible, but arithmetic bugs are *checkable*.
tkinter: windows that react
turtle still runs top-to-bottom; a GUI program instead builds widgets, then hands control to mainloop(), which waits — forever, until closed — for events like clicks, calling the functions you bound to them:
import tkinter as tk
count = 0
def clicked():
global count
count += 1
label.config(text=f"Clicks: {count}")
root = tk.Tk()
root.title("Click Counter")
label = tk.Label(root, text="Clicks: 0", font=("Arial", 16))
label.pack(padx=20, pady=10)
button = tk.Button(root, text="Click me!", command=clicked)
button.pack(pady=10)
root.mainloop()
Click the button and the label updates: mainloop noticed the click and invoked clicked, which mutated the label through its config method. Three ideas to carry away: widgets are objects (Lesson 10) configured with keyword arguments; pack() is the simplest layout manager, stacking widgets with padding; and event-driven code inverts control — you no longer dictate the sequence, you register responses. The functions you write are called *by the framework*, a pattern (callbacks) used by every GUI, web and game library in existence.
Common mistakes
command=clicked()with parentheses. That *calls* clicked immediately (during setup, once) and binds its return value — None — as the handler, leaving a dead button and one mysterious early execution. Bind the function itself:command=clicked. The same rule applies to every callback everywhere.- Forgetting
turtle.done()/mainloop(). Without them the window may flash and vanish before you see anything. - Turning angles by feel. Interior vs exterior angles differ (a square's corner is 90° either way by luck; a pentagon's exterior is 72°, its interior 108° — turning 108 closes nothing). Derive or test deliberately.
- Blocking the event loop. A long computation or
input()inside a callback freezes the whole window, because the loop cannot process events meanwhile. Keep callbacks fast; heavy work belongs elsewhere.
Real-World Challenge
The task. Build a small greeting app: a window with a name entry box, a Greet button, and a message label. Clicking Greet reads the entry and displays "Hello, <name>!" — or "Hello, stranger!" if the box is empty.
Worked solution.
import tkinter as tk
def greet():
name = entry.get()
message.config(text=f"Hello, {name}!" if name else "Hello, stranger!")
root = tk.Tk()
root.title("Greeter")
tk.Label(root, text="Your name:").pack(pady=(10, 0))
entry = tk.Entry(root)
entry.pack(padx=20, pady=5)
tk.Button(root, text="Greet", command=greet).pack(pady=5)
message = tk.Label(root, text="", font=("Arial", 14))
message.pack(pady=(0, 10))
root.mainloop()
How it works. The structure is identical to the click counter, which is the point — GUIs are a vocabulary, not a new language. Widgets are created top-to-bottom (prompt label, entry, button, message) and pack stacks them in creation order; the callback greet reaches the entry through the module-level name (defined before mainloop runs, so the reference resolves when the click happens, not when the button is created). The empty-name fallback is one conditional expression — input validation at its smallest, applying Lesson 2's thinking inside Lesson 18's widgets. entry.get() returns whatever the user typed as a string; nothing converts or sanitises it further, and for a greeting that is fine — a production form would strip whitespace and validate far harder.
Extension. Add a second button, "Clear", that empties the entry (entry.delete(0, tk.END)) and resets the message. Then restyle: explore font, fg (text colour) and bg options on the label, and a larger window via root.geometry("320x200"). The widget reference in the tkinter documentation lists every option — browsing it is how GUI programmers actually work.
Going further — rosettes, and thinking in the turtle's frame
One loop, one repeated shape, one steady turn: that recipe generates the intricate rosette patterns turtle graphics is famous for, and it is worth building once slowly. The turtle draws a circle, rotates by a fixed angle, and repeats — the pattern's symmetry is nothing but the arithmetic of 360 divided by the step:
import turtle
pen = turtle.Turtle()
pen.speed(0)
colors = ["red", "purple", "blue", "green", "orange"]
for i in range(36):
pen.color(colors[i % len(colors)])
pen.circle(60)
pen.left(10)
turtle.done()
Thirty-six circles at 10° steps complete the full turn, and the colour list cycles by modulo (i % len(colors)) — the same remainder arithmetic from Lesson 2, now painting. Every element generalises: circle accepts a radius (and an extent, for arcs), pen.pensize thickens lines, pen.speed(0) disables animation delays for instant rendering, and screen.bgcolor sets the canvas. The deeper skill underneath is frame-of-reference thinking: turtle commands are *relative* (turn from wherever you face), while goto and the screen's coordinate system are *absolute* (x right, y up, origin centre). Bugs in turtle programs are almost always frame confusions — code written as if the turtle were at the origin facing north when it is not. When a drawing goes wrong, the professional debugging move from Lesson 7 applies unchanged: slow the speed down, watch the state evolve step by step, and find the first moment reality departs from your mental picture.
Practice corner — the mathematics of going somewhere
Absolute movement (goto) and relative movement (forward/turn) meet in one formula worth owning: the point at angle θ and distance r from the centre sits at (r·cos θ, r·sin θ). Python's math module computes it (in radians — hence the conversion), which is how spiral and rosette variations are aimed precisely instead of by feel:
import math
for step in range(3):
angle = step * 40
x = 80 * math.cos(math.radians(angle))
y = 80 * math.sin(math.radians(angle))
print(step, round(x, 1), round(y, 1))
Output:
0 80.0 0.0
1 61.3 51.4
2 13.9 78.8
Feed those coordinates to pen.goto(x, y) inside the loop and the turtle hops around a circle of radius 80, forty degrees at a time; connect the hops and a polygon emerges from pure arithmetic. Trigonometry stops being abstract the first time it steers something on your screen.
🎬 Watch & Learn
Prefer video? These searches surface top tutorials on this lesson's topic:
- Python turtle graphics — draw with code (beginner tutorial) — pick a recent, well-reviewed video
- Tech With Tim: tkinter GUI tutorial for beginners — pick a recent, well-reviewed video
- freeCodeCamp: build a desktop app with Python tkinter — pick a recent, well-reviewed video
💪 Practice Zone
Try these yourself — hints are included, answers are not. Run your code in any Python environment.
- Square spiral — Draw a spiral: loop 50 times, move forward by a growing distance, turn 90° each time.
Hint
Increase the side length each iteration:for i in range(50): pen.forward(i * 4); pen.left(90). - Colorful bars — Adapt the Applied Track bar chart: give each bar a different color from a list.
Hint
Zip the data with a color list:for (day, value), color in zip(data.items(), colors):thenpen.fillcolor(color). - Greeting GUI — Build a tkinter app with a text entry and a button — clicking the button shows
Hello, <name>!in the label.Hint
Read the entry withentry.get()inside the button's callback function.
🌍 Real World
Data dashboards, scientific instruments, and internal business tools are often simple desktop GUIs like your tkinter app — quick to build, easy to use. Turtle's drawing commands are the same idea behind logo design tools and pen plotters that draw with real robots.