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.
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.
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.)
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).
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 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.
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.
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.
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.
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 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.
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.
Here's what the JavaScript visualizer does not do, so you can plan your lessons around it:
document, window, the DOM, alert(), prompt(), confirm(),
and imports of frontend libraries or frameworks such as jQuery or
React. This tool is only for learning how the core language executes.setTimeout and
setInterval, promises, async/await, and event handlers don't
work. Only synchronous code can be traced. Relatedly, there's no way
to read keyboard input (prompt() is a browser feature), and the
Date object isn't supported.let/const, arrow
functions, classes, template literals, Map and Set – but
syntax added to JavaScript after 2015 (e.g., async/await, object
spread {...obj}, optional chaining ?.) will not run.for loops: a counter declared inside a
for loop header with let shows up twice in the diagram (in two
nested blocks), due to how the underlying debugger reports block
scopes. The values shown are still correct, just visually
duplicated. For the cleanest display, declare the counter above the
loop or use a while loop, as the examples in this article do.parent: variables shown
for closures can occasionally be imprecise, since the underlying
runtime doesn't always expose complete closure data to tracing
tools.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.