What happens when you run a Python program

💡 Big Idea

A program is a list of instructions. When you run it, the computer reads those instructions one at a time, top to bottom, and does exactly what they say — nothing more, nothing less. Understanding this single idea will help you write better code, predict what your program will do before it runs, and find bugs faster when things go wrong.

1   What Actually Happens When You Click Run

When you click Run, press F5, or type python hello.py in the terminal, three things happen in order — and they always happen in this order:

  1. Python reads your file and checks for obvious mistakes. If it finds a missing colon, an unclosed bracket, or a misspelled keyword, it stops here and shows you an error. The program never starts.
  2. Python translates your code into a simpler set of instructions that the computer's processor can understand. You never see this step — it happens invisibly in a fraction of a second.
  3. The computer runs those instructions, line by line. Each line either does a calculation, stores a value, prints something, or decides what to do next.
🔍 Analogy: A Recipe

Think of your code like a recipe. Python is the chef who reads the recipe and does each step in order. If the recipe says "add 2 eggs," the chef adds exactly 2 eggs — not 3, not 0. The computer is equally literal. It does not infer what you meant. It does exactly what the code says.

2   Variables and Memory — What "Running" Actually Looks Like

To really understand what a running program is doing, you need to picture two things at the same time: the code you wrote, and the computer's memory as it changes. Every time Python runs a line, something in memory either gets created, updated, or used.

Here is a short program:

name = "Alex"
score = 0
score = score + 10
print(f"{name} has {score} points")

Here is what Python does, step by step:

LineWhat Python doesState of memory after this line
name = "Alex"Sets aside a chunk of memory, stores the text "Alex" in it, and labels that location name.name → "Alex"
score = 0Creates a new memory location labelled score and stores the number 0 in it.name → "Alex", score → 0
score = score + 10Looks up the current value of score (0), adds 10, and stores the result (10) back under the same label. The old 0 is gone.name → "Alex", score → 10
print(...)Looks up name and score, builds the string "Alex has 10 points", and sends it to the screen.Unchanged. Print does not modify memory.
💜 The most important habit: trace before you run

Before clicking Run, try to predict what every variable will contain after each line. If your prediction matches the output, you understood the code. If it does not match, you have found exactly what you need to learn. This habit — predicting before running — is the foundation of debugging.

3   Execution Order — Python Never Skips Lines

Python executes your code in the order you write it, top to bottom, one line at a time. It never skips a line, never runs line 4 before line 3, and never goes backwards — unless you use a structure that deliberately changes the flow.

There are three structures that change execution order:

StructureWhat it does to execution orderExample
if / elif / elseSkips some lines entirely, depending on whether a condition is true or false.If score >= 50 is false, the body of the if block is skipped completely.
for / while loopsRepeats a block of lines more than once.A for loop over a list of 5 items runs its body 5 times before moving on.
Function callsJumps to a different part of the file, runs those lines, then returns to where it left off.Calling greet("Alex") pauses the main program, runs the function, then resumes.
🔍 Why this matters for bugs

Almost every beginner bug comes from the program executing in a different order than the programmer expected. A variable that was never assigned because the if block was skipped. A loop that ran one too many times. A function that was called before its result was ready. When something goes wrong, the first question to ask is: "What order did Python actually run these lines in?"

4   Functions and the Call Stack

When your program calls a function, Python does not just jump to it and forget where it was. It carefully saves its current position, runs the function, and then returns to exactly where it left off. The mechanism it uses to track this is called the call stack.

def greet(user):
    message = f"Hello, {user}!"
    return message

name = "Alex"
result = greet(name)
print(result)

When Python reaches greet(name) on line 6, here is what happens:

  1. Python pauses the main program and records its current position (line 6) on the call stack — like placing a bookmark.
  2. Python creates a brand-new, separate memory space for the function. The variable user exists only inside the function. This separation is called scope.
  3. Python runs the function's lines. When it reaches return, it takes the value and hands it back to the main program.
  4. Python removes the bookmark and resumes from line 6, storing the returned value in result.
🔍 Analogy: Sticky Notes

The call stack is like a stack of sticky notes. Each time you call a function, Python puts a new note on top recording where to come back to. When the function finishes, it peels that note off and goes back to what was underneath. If you see an error message that says RecursionError: maximum recursion depth exceeded, it means a function kept calling itself and the stack of notes got so tall it fell over.

4.1   Scope — Why Variables "Disappear"

The variable message inside greet() only exists while that function is running. Once the function returns, that memory is cleared. If you tried to use message outside the function, Python would give you a NameError. This is intentional — it stops functions from accidentally reading or overwriting each other's data.

def greet(user):
    message = f"Hello, {user}!"
    return message

greet("Alex")
print(message)       # NameError: name 'message' is not defined
💜 Why scope is your friend

Scope can feel like an obstacle at first. It is actually a protection. Without it, every function you called could accidentally overwrite a variable you were using in your main program. Scope means that what happens inside a function stays inside the function — unless you explicitly return it.

5   Reading a Traceback — When Things Go Wrong

When Python crashes, it prints a traceback — a report showing the chain of function calls that led to the error. Understanding the call stack (Section 4) is what makes tracebacks readable. Every entry in a traceback is one bookmark Python had placed on the stack when things went wrong.

Traceback (most recent call last):
  File "mission.py", line 12, in <module>
    result = calculate_fuel(speed, distance)
  File "mission.py", line 7, in calculate_fuel
    return distance / speed
ZeroDivisionError: division by zero

Read a traceback in this order:

  1. Jump to the last line firstZeroDivisionError: division by zero tells you what went wrong.
  2. Read the last file/line referenceFile "mission.py", line 7, in calculate_fuel tells you where the crash happened.
  3. Scroll upward only if needed — the upper entries show you how the program got there, which matters when the bug is in the data being passed into a function rather than in the function itself.
💜 The traceback is the call stack, printed in reverse

The bottom of the traceback is the innermost function — the one that actually crashed. The top is the outermost call — usually the main part of your program. Reading bottom-to-top tells you what happened; reading top-to-bottom tells you the path that got you there. You will rarely need to read the whole thing. Start at the bottom and work up only as far as you need to.

6   Under the Hood — Bytecode and the Interpreter

When Python reads your .py file, it compiles it to bytecode — a compact, simplified set of instructions stored in a .pyc file inside the __pycache__/ folder that appears in your project directory. You never need to open or edit these files.

That bytecode is then run by the CPython interpreter — a program written in the C language that reads each bytecode instruction and carries it out. This is why Python is called an "interpreted language": every line of your code passes through this interpreter layer, which takes time. A language like C or Rust compiles directly to machine code the processor can run without an interpreter in the middle — which is why those languages are faster.

🔍 What this explains in practice

Three things you will notice that this layer explains:
Startup cost: Python takes a moment to start even for a tiny script — that is the compile-to-bytecode step.
The __pycache__ folder: Python saves the compiled bytecode so it does not have to recompile every run. It is not a bug or an error — leave it alone.
"Python is slow": Loops that run millions of times are slow in pure Python. Libraries like NumPy are fast because their inner loops are written in C — Python just calls them.

💜 Do you need to know this in Grade 9?

Probably not yet. But when your Flask app takes half a second to restart, when a loop over a large dataset feels sluggish, or when you see __pycache__ appear and wonder what it is — this is the answer. You will also hear phrases like "Python is slow" or "use NumPy for this" in the programming world. Now you know why those statements are true and what they actually mean.

7   Putting It Together — The Four Layers

Here is a summary of how all four layers connect, from the code you write to the result on the screen:

LayerWhat happens hereWhat goes wrong here
1. Your codeYou write instructions in Python syntax.Logic errors — wrong answer, silent wrong behaviour.
2. Parse and checkPython reads your file and checks for syntax mistakes before running anything.SyntaxError, IndentationError — program never starts.
3. Execution (line by line)Python runs each instruction, updating memory as it goes, jumping for functions, repeating for loops, branching for conditionals.NameError, TypeError, ZeroDivisionError, etc. — program crashes mid-run.
4. Bytecode / InterpreterPython compiles your code to bytecode and runs it through the CPython interpreter. Cached in __pycache__.Performance issues, not correctness issues. Rarely a source of bugs at Grade 9.
💡 Big Idea

A running program is just a list of instructions being carried out one at a time, updating memory as it goes. Python never skips lines, never guesses, and never does what you meant — only what you wrote. When something goes wrong, the question is always: "What order did Python run these lines in, and what was in memory at that moment?" That question, asked consistently, is the foundation of debugging.

✅ Check Your Understanding
  1. When you run a Python program, what are the three steps that happen in order? Describe each one in your own words.
  2. Trace through this program on paper. Write down the value of every variable after each line runs:

    total = 100
    tax_rate = 0.10
    tax = total * tax_rate
    total = total + tax
    print(total)
  3. Explain in one sentence: why does a variable created inside a function not exist outside it?
  4. A student sees this error and panics. What are the two most important things to read first, and what does each one tell you?

    Traceback (most recent call last):
      File "shop.py", line 9, in <module>
        total = calculate_total(prices)
      File "shop.py", line 4, in calculate_total
        return sum(prices) / len(prices)
    ZeroDivisionError: division by zero
  5. You notice a __pycache__ folder in your project directory. Should you delete it? Explain why or why not in one sentence.