How the Python Tutor visualizer can help students in your Python courses

Summary: This article is meant for instructors who teach Python, whether in university or K-12 classrooms. Python Tutor is a free web-based tool that draws step-by-step visualizations of code as it runs. Since 2010, over 25 million people in over 180 countries have used it to visualize over 500 million pieces of code. This article shows what it can illustrate at every level of Python teaching, from a student's first loop up through closures and generators. It will always remain free to use.

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.

The Basics: Variables, Loops, and Conditionals

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.

Functions and the Call Stack

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).

Lists and the Famous Aliasing Surprise

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.

Dictionaries, Tuples, and Sets

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.

Nested Data Structures

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:

Objects and Classes

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.

Recursion

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.

Exceptions

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.

More Advanced Python

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.

Please Help Spread The Word!

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.