Python Programming — Alpha School

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.

Key idea. Key idea: Turtle makes loop state visible and rewards precise geometry; tkinter introduces event-driven programming — build widgets, bind callbacks with command=function_name, and let mainloop dispatch events.

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.
Try it. Try it: Extend draw_polygon into draw_star(pen, size) using the 144° turn, then draw three stars of sizes 50, 100 and 150 across the screen (penup/goto between them). What single parameter patterns from Lesson 4 made this a five-minute task?

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:

💪 Practice Zone

Try these yourself — hints are included, answers are not. Run your code in any Python environment.

  1. Square spiral — Draw a spiral: loop 50 times, move forward by a growing distance, turn 90° each time.
    HintIncrease the side length each iteration: for i in range(50): pen.forward(i * 4); pen.left(90).
  2. Colorful bars — Adapt the Applied Track bar chart: give each bar a different color from a list.
    HintZip the data with a color list: for (day, value), color in zip(data.items(), colors): then pen.fillcolor(color).
  3. Greeting GUI — Build a tkinter app with a text entry and a button — clicking the button shows Hello, <name>! in the label.
    HintRead the entry with entry.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.

Keep reading to complete this lesson