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

Summary: This article is for instructors who teach JavaScript, whether in high school classrooms (e.g., AP Computer Science Principles), university, or coding bootcamps. Despite its name, Python Tutor is a widely-used web-based visualizer for JavaScript that helps students to understand and debug their code step-by-step. The JavaScript visualizer will always remain free to use.

Read first: one-page quick-start guide for teachers


Python Tutor is a free tool that has been used by tens of millions of people since 2010 to visualize and debug code step-by-step. Despite its name, it visualizes more than just Python: I added JavaScript support back in 2015, and it also does Java and C/C++. This article shows instructors what the JavaScript visualizer can illustrate, from a student's first loop up through closures.

One thing to be clear about up front: this tool teaches the JavaScript language, not web development. JavaScript is the language that runs in every web browser, and through Node.js it runs on more servers and command-line tools too, which probably makes it the most widely-run programming language in the world. Everything your students will eventually do with HTML, CSS, and the DOM rests on top of the core language: variables, functions, arrays, objects, and closures. That core is what this tool visualizes.

These visualizations fit naturally into high school AP Computer Science Principles (CSP) classes. AP CSP doesn't require any particular programming language – the exam presents code in the College Board's own pseudocode – but JavaScript is one of the most popular languages that CSP curricula teach with: for instance, Code.org's CSP curriculum has students program apps in JavaScript, and CodeHS offers an AP CSP course taught in JavaScript. If your CSP students write JavaScript, this tool lets them trace their code's execution line by line, which is the same skill that the exam's code-reading questions test in pseudocode form.

If you think this tool may be helpful for your colleagues or students, share this direct link in relevant course materials, chat groups, mailing lists, discussion forums, or social media:

All examples below are interactive: drag the slider under each one to step forward and back through execution, and click "Edit Code" to open it in the full editor.

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 12), we're partway through the loop: day is 2 and total has just grown to 50. Step forward to watch day 3 take the other branch of the if statement and add the discounted price of 20 instead, so total ends at 70. Every value this program ever computes is one slider-drag away, which beats re-running code over and over with extra console.log calls sprinkled in.

There's nothing to install and no account to create: students go to the JavaScript visualizer, type code, and press "Visualize Execution." Or they can click a link you send to them.

Making Type Quirks Visible

JavaScript is famous for its loose typing, and the visualizer shows each value with its type made visible: strings display in quotes, numbers don't, and JavaScript's special values display by name. Here's a little quiz to give your students: what value does each variable hold when this program ends?

The global frame shows all of the answers at a glance. The + operator concatenates when either side is a string, so b is the string "12", but the - operator coerces to numbers, so c is the number 9. Division produces 2.5 – there is no separate integer division to worry about, since every JavaScript number is a float. And the language's two different "empty" values display distinctly: nothing is null (deliberate emptiness) while missing is undefined (declared but never assigned).

Line 6 holds the classic floating-point surprise: same is false. Click "Next >" to run the final line and look at the printed output: sum is actually 0.30000000000000004, not 0.3. (The frame display rounds small numbers to six digits for readability, but printing reveals the full value.)

Functions and the Call Stack

Each function call gets its own frame that holds its parameters and local variables:

At the current step (Step 7), the second call to priceWithTax is running: its frame holds price = 25 and tax = 2, while the first call's result (54) already sits in the global variable jeans. Step forward to watch this call hand back its return value of 27 and its frame disappear.

Also notice the top right of the diagram: the global name priceWithTax points to a function object. In JavaScript, functions are values just like numbers and arrays are, which matters a great deal later (see the sections on higher-order functions and closures).

Arrays and the Famous Aliasing Surprise

Why does a print as [ 1, 2, 3, 4 ] when this code only ever pushed onto b?

Because let b = a; doesn't copy anything: both variables now refer to the same array object, and at the current step (Step 4) the diagram shows exactly that – two arrows, one array. By contrast, step forward to line 6, where a.slice() creates a genuine copy that appears as a second array on the right, so pushing onto c leaves a alone.

The same picture settles what happens when arrays are passed to functions:

At the current step (Step 7), replaceAll has just re-pointed its local parameter arr at a brand-new ["coffee"] array, and there are now two separate arrays on screen: groceries still points at the original one (which addItem successfully grew with "eggs" earlier), while the new array is about to vanish when the function returns. This one picture settles the perennial "does JavaScript pass by value or by reference?" argument by just showing what happens: the parameter binds to the same array that the caller passed in, so mutating the array is visible to the caller, but reassigning the parameter is not.

(You may also notice the frame's return value of undefined: a JavaScript function that ends without a return statement always returns undefined, which is itself a common source of student confusion that this display makes visible.)

Objects, Maps, and Sets

Objects render with their properties laid out as labeled boxes, and arrows show which variables share which objects:

At the current step (Step 6), users points at an array whose two elements point at the very same objects that alice and bob point at – no copies. That's why bob.age = 31 on line 5 is visible through users[1] too. And line 6 just added a brand-new city property to Alice's object at run time, something that surprises students coming from Java, where an object's fields are fixed by its class. Click "Next >" to finish: users[0].city prints San Diego, proving that users[0] and alice are one and the same object.

ES6's Map and Set collections render cleanly too:

At the current step (Step 7), the ages map shows that ages.set("alice", 27) updated the existing entry for that key rather than adding a second one. Step backward one step to catch tags right after its creation: even though the code listed "js" twice, the set stored it only once. Click forward to the end and the program prints 27 and 3.

Debugging: the Off-by-One That Prints NaN

In Java, reading past the end of an array throws an exception that points at the guilty line. JavaScript is more permissive, which usually confuses novices more: an out-of-bounds read quietly produces undefined, arithmetic on undefined produces NaN, and NaN then spreads to everything it touches. Students see NaN in their final output with no idea where it was born. The visualizer shows the exact step:

This loop is supposed to average three test scores, but its condition says <= where it should say <. At the current step (Step 14), the first three additions went fine – total is 255 – but i is now 3, and the next line is about to read scores[3], which doesn't exist. Click "Next >" and watch total become NaN at the exact moment 255 is added to undefined. From there the damage is permanent, and the program ends by printing average: NaN. Being able to rewind and pinpoint the step where a good value went bad is precisely the debugging skill we want students to build.


If you teach beginners – including AP CSP – the sections above may already cover your whole course. The rest of this article goes deeper into the language: classes, recursion, higher-order functions, closures, and hoisting.

Classes and Object-Oriented Programming

When your course reaches ES6 classes, the visualizer draws the class, its instances, and the this reference inside method calls:

At the current step (Step 10), alice.deposit(25) is executing. The deposit frame's this points at Alice's object (not Bob's), and her balance is still 100. Step forward to watch it become 125 while Bob's stays at 50 – the two instances hold separate state even though they share one class. Step backward to the beginning to watch each new BankAccount(...) call run the constructor, where this points at the object being born.

Inheritance and polymorphism display too:

At the current step (Step 18), the loop's second iteration is calling pets[i].speak(), and because pets[1] is a Dog, execution has jumped into Dog's version of speak() on line 15, with this pointing at Rex. Drag the slider back to Step 12 and the very same call site dispatches to Animal's version on line 6 instead, since pets[0] is a plain Animal. That is polymorphism, shown rather than told. Also rewind to Step 6 to see new Dog("Rex") chain two constructor frames: Dog's constructor immediately calls super(name), which runs Animal's constructor – and notice that Dog's constructor frame doesn't even have a this until super() returns at Step 8, which is exactly the rule that a subclass must call super() before touching this.

Recursion

Since every recursive call gets its own frame with its own copy of the parameters, students can watch the call stack build up and then unwind. Here is the classic factorial example:

At the current step (Step 9), four frames of factorial are stacked up, each holding its own n (4, 3, 2, 1), and the base case is about to return 1. Step forward to watch each frame hand its result down to its caller – 1, then 2, then 6 – until the original call returns 24. Recursion stops feeling like magic once each call is visibly just another frame.

Higher-Order Functions and Arrow Functions

Since functions are values, they can be passed to other functions. Here applyToEach takes an array and a function f, and applies f to every element:

At the current step (Step 14), the diagram shows the whole mechanism: the global double and the parameter f are two arrows pointing at one arrow-function object (x => x * 2), and that function is mid-call with x = 20, about to return 40 into the growing results array. (Arrow functions are anonymous, so their frames display without a name.) Step through the rest to watch the third call compute 60.

The same frames appear when you use JavaScript's built-in array methods:

At the current step (Step 12), map has already finished building the new [ 20, 40, 60 ] array, and filter is mid-run, testing x = 20 against x > 15. Each call to your arrow function gets a real frame on the stack, even though the loop driving it lives inside the built-in method. Note also that nums itself never changes: map and filter return new arrays, another fact students can confirm with their own eyes here.

Closures

Closures are where JavaScript courses tend to lose people, and they're also something that almost no other tool can draw. Here is the classic counter factory:

makeCounter returned long ago, yet its count variable is still alive. The global tickets points at the inner increment function object, and every call to tickets() gets a frame containing an entry labeled parent:count – the parent: prefix means this variable doesn't live in the call's own frame, but in the enclosing scope that increment captured when it was created. At the current step (Step 11), the second call has just ticked parent:count up to 2; step forward and the third call returns 3, which is what gets printed. One picture shows the whole idea: a function can carry private, persistent state with it. This is the concept underneath callbacks that remember things, module patterns, and every "why did my variable keep its old value?" question, so it's worth showing students even before they can articulate what a closure is.

Hoisting and var vs. let

Why do modern JavaScript courses teach let and const instead of var? Here's one reason:

At the current step (Step 2), line 1 has already run – and it printed undefined rather than crashing, even though greeting isn't assigned until line 2. Look at the global frame: greeting existed before the program's first line ever ran. That's hoisting: JavaScript moves var declarations (but not their assignments) to the top of their scope before execution begins. Now click "Edit Code" under the example, change var to let, and re-run: the program instead stops with ReferenceError: greeting is not defined, which is the stricter, saner behavior that let and const were added to provide.

What It Does Not Support

Here's what the JavaScript visualizer does not do, so you can plan your lessons around it:

Please Help Spread The Word!

The JavaScript visualizer in Python Tutor can help your students see what their code actually does one step at a time, and build a solid mental model of the language before they take on the rest of web development. It's free, it runs in the browser with nothing to install, and it has been used by tens of millions of people.

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 Python, Java, or C/C++, check out my companion articles on what the Python visualizer, Java visualizer, and C/C++ visualizer can do.