← Back to Blog
    LearningOctober 24, 20265 min read

    Python Code Visualizer: Watch Your Code Run Line by Line

    See Python variables, lists and function calls as a live diagram. A guide to the free Python visualizer, with four bugs that become obvious once you can see them.

    On this page

    When Python code does something you didn't expect, the problem is rarely the syntax. It is the picture in your head of what the code does. You think b = a makes a copy, so you are surprised when changing b also changes a. You think a function can't touch the list you passed in, so you are surprised when it does.

    A code visualizer fixes that picture. Instead of guessing, you run the program one line at a time and see every variable, every list and every function call as a diagram. This guide shows how to use the free Python visualizer on PythonExecutor and walks through four bugs that become obvious the moment you can see them.

    What the visualizer shows

    Paste a program, press Visualize, and the page splits into three parts:

    • Program: your code, with an arrow on the line that runs next and a lighter arrow on the line that just ran.
    • Frames: the variables that exist right now. The main program has one frame, and every function call gets a new frame stacked on top.
    • Objects: the lists, dictionaries, sets, functions and class instances your program has created. Arrows run from each variable to the object it refers to.

    Anything that changed on the current step is outlined, so your eye goes straight to it. Printed output appears underneath, as it happens.

    Use Step and Back to move one line at a time, or drag the slider to jump anywhere. On a keyboard, the left and right arrow keys step backwards and forwards, and Home and End jump to the start and the finish. Going backwards is the superpower here: when something goes wrong, you can step back to the exact moment it happened.

    Example 1: two names, one list

    This is the most common surprise in Python:

    a = [1, 2]
    b = a
    b.append(3)
    print(a)
    [1, 2, 3]

    Many beginners expect [1, 2]. Step through it in the visualizer and the reason is plain to see: after b = a there is still only one list in the Objects column, with two arrows pointing at it. b = a doesn't copy anything. It gives the same list a second name, so appending through b changes the list that a points to as well.

    If you want a separate list, make a copy:

    a = [1, 2]
    b = a.copy()
    b.append(3)
    print(a, b)
    [1, 2] [1, 2, 3]

    Now the diagram shows two lists and two arrows going to different places. (list(a) and a[:] make a copy too.)

    Example 2: what a function can and can't change

    def add_item(basket, item):
        basket.append(item)
        basket = ["new list"]
        return basket
    
    shopping = ["milk"]
    result = add_item(shopping, "eggs")
    print(shopping)
    print(result)
    ['milk', 'eggs']
    ['new list']

    Step into the call and watch the Frames column. A new add_item frame appears, and its basket arrow points at the same list as shopping. So basket.append(item) changes the shopper's list: the outline lights up on the shared object.

    On the next line, basket = ["new list"] creates a brand new list and moves only the local basket arrow to it. shopping still points at the original. When the function returns, its frame disappears, and the only trace left of the new list is result.

    This one picture explains a rule that otherwise takes a paragraph to state: a function can change an object you pass in, but assigning to the parameter name never affects the caller's variable.

    Example 3: recursion as a stack of frames

    Recursion feels like magic until you watch it:

    def factorial(n):
        if n <= 1:
            return 1
        return n * factorial(n - 1)
    
    print(factorial(4))
    24

    As you step, the Frames column grows a tower: factorial with n = 4, then n = 3, n = 2 and n = 1, each waiting for the one above it. At the top, n <= 1 is finally true and the deepest call returns 1. Then the tower comes down one frame at a time as each call multiplies and returns: 2, 6, 24.

    Seeing every waiting call at once makes two things obvious: why every recursive function needs a base case (without one the tower never stops growing), and why very deep recursion runs out of room.

    Example 4: the grid that changes every row

    Here is a bug that catches even experienced programmers:

    grid = [[0] * 3] * 3
    grid[0][0] = 1
    print(grid)
    [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

    One assignment changed all three rows. The diagram shows why at a glance: the outer list holds three arrows to the same inner list. Multiplying a list copies the references, not the row.

    The fix is to build a new row each time:

    grid = [[0] * 3 for _ in range(3)]
    grid[0][0] = 1
    print(grid)
    [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

    Visualize both versions one after the other and compare the Objects column: one inner list in the first, three in the second.

    Tips for learning with it

    • Predict before you step. Before each click, say out loud what you expect to change. The moments you are wrong are the moments you learn something.
    • Keep programs small. Ten to twenty lines is the sweet spot. Cut a bigger program down to just the part that confuses you.
    • Step backwards from the bug. Jump to the end with End, then press the left arrow until the wrong value first appears.
    • Use it alongside print(). Printing tells you what a value is; the diagram tells you which object it is, and who else shares it.

    What it supports

    The visualizer runs a built-in Python interpreter in your browser, so nothing is installed and your code never leaves your computer. It covers the core language: variables, if, loops, functions, recursion, classes, lists, tuples, dictionaries, sets, exceptions, f-strings, and the math and seeded random modules. Programs that ask for input() can be given their answers in the Input lines box, one per line.

    To keep the diagrams readable, runs stop after 1,000 steps, and a few advanced features aren't supported: decorators, with, generators (yield), async and imports other than math and random. For those, use the full Python playground.

    Try it now

    Open the Python visualizer, paste in any example from this post, and press Visualize. When you're ready to test what you've learned, the Python challenges give you 32 problems from very easy to hard, and the free Python course teaches the basics from scratch.

    By PythonExecutor Team

    Related Articles