Read first: one-page quick-start guide for teachers
Python Tutor is a free tool that visualizes what your code does step-by-step as it runs. Since I created it in 2010, over 25 million people in over 180 countries have used it to visualize over 500 million pieces of code, and it's used for teaching in over 10,000 schools, including MIT, Harvard, Princeton, UC Berkeley, Tsinghua, and the IITs.
This article shows what it can illustrate across the full range of Python courses, from a student's very first loop up through closures and generators.
If you think this tool may be helpful for your students, please share this direct link in relevant course materials, chat groups, mailing lists, discussion forums, or social media:
This article starts with first-semester topics and works its way up. If you teach beginners, the first half may be all you need – feel free to stop whenever it goes past your course's level. And if you're more experienced, check out the "More Advanced Python" section near the end to see concepts (closures, generators) that other tools usually can't draw. All examples below are interactive: drag the slider under each one to step forward and back through execution, and click "Edit Code" under any example to open it in the full editor, where you and your students can modify and re-run it.
When students learn their first loops, teachers often have them trace code by hand, keeping a little table of each variable's value at each step. Python Tutor works like a self-updating trace table:
At the current step (Step 10), we're partway through the loop: day is
2 and total is 50. Students can step forward and backward to see
every value this program ever computes. There's nothing to install or set up:
students go to the website, type code, and press Visualize. Or they can
click a
link you send to them.
Programs that read keyboard input work too. When execution reaches a
call to input(), the visualizer prompts for a value, and entered
values are saved so students can edit and re-run without retyping.
Here's a loop that reads three test scores and averages them:
The "User inputs" panel below the code lists all three values that this
run was given. At the current step (Step 7), the first two are crossed
out in gray because the program has already consumed them: input()
has run twice, and the second score is in s, about to be appended to
the list. Step backward and forward to watch each call to input()
cross off exactly one value at the moment it's read.
Each function call gets its own frame that holds its local variables:
At the current step, the frames for both checkout and add_tax are
on screen at once. Stepping through shows values flowing into
parameters and back out through return values (each function's return
value is displayed just before its frame disappears).
In the example below why does a print as [1, 2, 3, 4] when the code only ever appended
to b?
Because b = a doesn't copy anything: both names now refer to
the same list object, and the diagram shows exactly that – two
arrows, one list. By contrast, list(a) creates a genuine copy, which
appears as a second list on the heap.
The same idea explains what happens when lists are passed to functions:
In the current step, replace_all has just rebound its local name
lst to a brand-new list, and there are now two separate lists on the
heap: groceries still points at the original one (which add_item
successfully mutated earlier), while the new ['coffee'] list is about
to vanish when the function returns. This one picture settles the
perennial "is Python pass-by-value or pass-by-reference?" argument by
just showing what happens: the parameter name binds to the same object
that the caller passed in, so mutating the object is visible to the
caller, but rebinding the name is not.
Here is the classic counting pattern, with the dictionary growing key by key:
At the current step, counts already holds entries for 'cat' and
'dog', and students can watch each loop iteration either add a new
key or bump an existing count, which is the exact distinction that the
if animal in counts test exists to make. Tuples and sets render
similarly (you'll see a tuple in the next example), as do types from
the collections module such as Counter and namedtuple.
Realistic data is nested: lists of dictionaries, dictionaries of lists, tuples inside both. This is where Python Tutor's diagrams really beat looking at text printed to your terminal:
In the above example the loop variable s points at whichever student
record is currently being processed: at the current step it's Bob's
dictionary, and his 75.0 average is about to lose to Ada's 92.5. Once
students can see the arrows, they can evaluate an expression like
s['scores'][0] just by following arrows in the diagram.
For list-of-lists there's also an optional grid display (the "show list-of-lists as 2D array" checkbox below the code editor), which is useful for nested loops and matrix problems:
When your course reaches object-oriented programming, the visualizer can draw classes, instances, and methods:
At the current step, alice.deposit(25) is executing. The deposit
frame's self points at Alice's account object (not Bob's). The two
accounts hold separate state; alice.balance has already changed to 125
while bob.balance remains 50.
Here's an example of inheritance and method overriding:
The class objects themselves appear on the heap, with Dog marked as
extending Animal. At the current step, the loop is calling speak()
on Rex, so self points at the Dog instance and Python dispatches
to Dog's overriding method. Step backward one iteration to watch the
very same call site dispatch to Animal's version instead.
Since every recursive call gets its own frame with its own copy of parameters, students can see the call stack building up then unwinding:
At the current step, four frames of factorial are stacked up, each
holding its own n, and the base case is about to return 1. Stepping
forward shows each frame handing its result down to its caller until
the original call returns 24.
One related option worth knowing about: "show frames of exited functions" (under advanced options) keeps every exited frame on screen in gray so that after the program ends you can review the complete history of calls.
Exception handling is also a form of control flow, so it benefits from the same step-through visualization:
Step through the second call to watch the red arrow jump from the failing
division directly into the except block. Uncaught exceptions display
too: the visualizer marks the exact step where the exception occurred,
with every variable still on screen to help students debug.
Everything below this point is for courses that go beyond the intro level.
First, closures are something that almost no other tool draws correctly:
make_counter returned long ago, yet its frame lives on (shown in light gray) because the
nested increment function still refers to its count variable. The
visualizer draws increment's frame with an explicit link to that
parent frame, where you can watch count tick up to 3 across three
separate calls. nonlocal, lexical scoping, and the whole idea that
"functions carry their birth environment with them" – all in one
picture. (If you teach decorators, this diagram can also show how they work.)
The tool also visualizes generators:
Each time the for loop asks for another value, the countdown frame
comes back onto the stack, resumes where it left off, and shows the
value it's about to yield. Watching a generator's frame suspend and
resume demystifies what yield actually does.
And for the functional programming parts of the language:
At the current step, sorted is repeatedly calling the lambda, which
runs in its own frame with its own n. Step backward and you'll see
that the list comprehension on Line 2 also ran in its own <listcomp>
frame, which is exactly how Python scopes comprehension variables.
In addition, default arguments, *args and **kwargs, and even
circular references display correctly (e.g., lst.append(lst) renders
as a list pointing to itself). Lastly, the tool can also visualize more
advanced concepts like iterator protocols, dunder methods, descriptors,
context managers, MRO (method resolution order), ABC (abstract base
classes), and metaclasses.
Python Tutor can help your students see what their code actually does. It's free, it runs in the browser with nothing to install, and has been used by tens of millions since 2010.
Feel free to share this direct link in relevant course materials, chat groups, mailing lists, discussion forums, social media, or anywhere else:
And if you also teach Java, C/C++, or JavaScript, check out my companion articles on what the Java visualizer, C/C++ visualizer, and JavaScript visualizer can do.