Skip to main content

Understanding the Python REPL - The Execution Engine You Already Have

Reading time: ~14 minutes | Level: Foundation → Engineering

Quick: what does this REPL session print and why?

>>> x = 10
>>> x = x + 1
>>> x
>>> print(x)

Line 3 (x) prints 11. Line 4 (print(x)) also prints 11.

But they work differently. Line 3 is the REPL printing the expression result. Line 4 is print() writing to stdout. Two different mechanisms. Same visual outcome.

If that distinction is blurry, you do not fully understand the REPL - and you are missing one of Python's most powerful debugging and exploration tools.

What You Will Learn

  • What REPL stands for and what each phase actually does
  • How Python compiles and executes code even in interactive mode
  • Why state persists between REPL lines - and what that means for debugging
  • The _ underscore variable: Python's automatic result storage
  • Multi-line input, indentation, and how REPL handles incomplete code
  • How expressions and statements differ in REPL output
  • IPython: the professional REPL that changes your workflow
  • Real engineering use cases where REPL beats writing scripts

Prerequisites

  • Python installed (see previous lesson)
  • A terminal you can type commands into
  • No prior REPL experience required

The Mental Model: A Loop Inside the Interpreter

Four phases. Repeat until the user exits.

The critical insight: Eval is not interpretation. Even in interactive mode, Python goes through the full compilation pipeline - tokenization, AST, bytecode - before executing anything. The difference from running a script file is only timing: REPL compiles and executes one statement at a time, immediately.

Watch: Python REPL and Interactive Mode

:::info Video Covers the REPL in depth, including how expressions vs statements work, state persistence, and common interactive usage patterns. :::

Part 1 - Starting the REPL and Reading Input

python3

You see:

Python 3.11.9 (main, Apr 2 2024, 08:25:00)
[GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

The >>> prompt is the REPL waiting for input. Three right-angle brackets. The Python logo.

The Read phase reads everything you type until it detects a complete syntactic unit. For a single expression like 2 + 3, one line is enough. For a function definition, it needs multiple lines.

Part 2 - The Eval Phase: Full Compilation Happens Here

This is the phase most people misunderstand.

When you press Enter, Python does not "interpret" your code in some simplified way. It runs the full pipeline:

>>> 2 + 3

What happens:

  1. Tokenize: 2, +, 3 → three tokens
  2. Parse: tokens → BinOp(Constant(2), Add(), Constant(3)) AST node
  3. Compile: AST → bytecode instructions
  4. Execute: PVM evaluates the bytecode, produces 5

You can see the bytecode yourself:

>>> import dis
>>> dis.dis(compile("2 + 3", "<stdin>", "eval"))
1 0 LOAD_CONST 0 (2)
2 LOAD_CONST 1 (3)
4 BINARY_OP 0 (__)
6 RETURN_VALUE

Four bytecode instructions for 2 + 3. REPL does not skip the compiler - it is the compiler, executing incrementally.

tip

Understanding that REPL compiles code before executing it explains why syntax errors appear instantly (parse phase) but logic errors appear only when that line executes.

Part 3 - The Print Phase: Expressions vs Statements

This is the subtle distinction that trips up beginners.

Expressions produce a value and the REPL displays it:

>>> 2 + 3
5

>>> "hello".upper()
'HELLO'

>>> [1, 2, 3]
[1, 2, 3]

>>> True and False
False

Statements perform actions and return None. The REPL does not display None:

>>> x = 10
>>> ← no output! Assignment returns None

>>> def greet():
... return "Hi"
...
>>> ← no output! def is a statement

>>> import sys
>>> ← no output! import is a statement

The rule: REPL prints the result of expressions. It suppresses None.

Test this:

>>> None
>>> ← suppressed (it's None)

>>> print("hello") ← this IS an expression that prints as a side effect
hello
>>> ← but print() returns None, so no REPL output after

print("hello") calls print (a function), which writes to stdout as a side effect, then returns None. The REPL sees None and suppresses it.

Part 4 - State Persistence: The REPL Session Is Alive

Every REPL session maintains a namespace - a dictionary of names to objects. Names you define persist across all lines in the same session.

>>> x = 10
>>> y = 20
>>> z = x + y
>>> z
30

>>> def double(n):
... return n * 2
...
>>> double(z)
60

>>> numbers = [1, 2, 3]
>>> numbers.append(z)
>>> numbers
[1, 2, 3, 30]

All of this is live in memory. The session namespace is equivalent to the global namespace of a running Python module.

NameBound to
xint object (10)
yint object (20)
zint object (30)
doublefunction object
numberslist object [1,2,3,30]

Hidden State Is a Real Danger

Because state persists, you can accidentally test code with variables from previous experiments:

>>> data = [1, 2, 3]
>>> # ... some experiments ...
>>> data = None ← you forgot you did this
>>> # ... more experiments ...
>>> len(data) ← AttributeError: 'NoneType' object has no attribute ...

Restart the REPL to get a clean state when your experiments become entangled. Ctrl+D (or exit()) to exit, then reopen.

warning

The REPL session state is the most common source of "it works in REPL but not in my script" bugs. If your script does not define something that was in your REPL session, the script fails. Always test final code in an isolated script.

Part 5 - The Underscore Variable _

Python automatically stores the result of the last expression in the special name _:

>>> 10 + 20
30
>>> _
30

>>> "hello".upper()
'HELLO'
>>> _
'HELLO'

>>> [1, 2, 3]
[1, 2, 3]
>>> len(_) ← _ holds the list from the previous expression
3

This is useful for quick follow-up operations without assigning a name:

>>> some_complex_calculation()
1234567

>>> _ * 2 ← use the previous result
2469134

>>> result = _ ← give it a name if you need to keep it
note

_ is a real variable name in Python's namespace. It works in REPL sessions specifically because REPL assigns to it after each expression. In regular scripts, _ is just a conventional name for "don't care" values (e.g., in unpacking: first, _, last = (1, 2, 3)).

Part 6 - Multi-Line Input

When you type a statement that requires multiple lines, the REPL shows ... (the continuation prompt):

>>> def greet(name):
... message = f"Hello, {name}!"
... return message
...
>>> greet("Engineer")
'Hello, Engineer!'

The REPL knows the function definition is incomplete after line 1 (the colon signals a block is coming). It keeps waiting until you enter an empty line, signaling the block is done.

Same for if statements, for loops, class definitions, and with blocks:

>>> for i in range(3):
... if i % 2 == 0:
... print(f"{i} is even")
...
0 is even
2 is even
tip

If you get stuck in ... mode and want to cancel, press Ctrl+C. This raises KeyboardInterrupt and returns you to >>>.

Part 7 - REPL vs Script: When to Use Which

REPLScript File
Quick experimentsProduction code
Exploring library APIsReproducible execution
Debugging isolated logicImporting as module
Checking object behaviorAutomated runs
One-off calculationsTeam-shared code
Learning new syntaxCI/CD pipelines

The REPL is not a replacement for writing code in files. It is a verification and exploration tool. Engineers use both constantly.

Watch: Python Variables and Interactive Exploration

Part 8 - IPython: The Professional REPL

The standard Python REPL is functional but limited. IPython is what professional engineers actually use for interactive work.

Installing IPython

pip install ipython
ipython

What IPython Adds

Tab completion:

In [1]: import numpy as np
In [2]: np.<TAB>
# Shows: np.array, np.zeros, np.ones, np.linspace, ...

Magic commands:

In [1]: %timeit [x**2 for x in range(1000)]
# 156 µs ± 2.3 µs per loop

In [2]: %who
# Lists all variables in namespace

In [3]: %run my_script.py
# Run a script inside IPython, keeping its namespace

In [4]: %%time
...: result = sum(range(1_000_000))
# Shows wall time for the entire cell

In [5]: %paste
# Pastes clipboard content and executes it

Question mark for documentation:

In [1]: str.split?
# Shows docstring and signature

In [2]: str.split??
# Shows source code if available

History and output numbering:

In [1]: 2 + 2
Out[1]: 4

In [2]: "hello"
Out[2]: 'hello'

In [3]: Out[1] + Out[1] ← access previous outputs by number
Out[3]: 8

Error tracebacks: IPython shows colorized, more readable tracebacks.

Jupyter Notebooks

IPython powers Jupyter Notebooks - the standard tool for data science, ML research, and exploratory analysis. The notebook interface runs IPython kernels, giving you the same interactive capabilities with visual output of plots and formatted data.

Part 9 - Engineering Use Cases

Exploring an Unfamiliar Library

>>> import pandas as pd
>>> df = pd.DataFrame({'a': [1,2,3], 'b': [4,5,6]})
>>> type(df)
<class 'pandas.core.frame.DataFrame'>
>>> dir(df)
>>> df.dtypes
>>> df.describe()

You learn the API by touching it, not by reading documentation for 20 minutes.

Verifying Memory Behavior

>>> a = [1, 2, 3]
>>> b = a
>>> id(a) == id(b)
True
>>> b.append(4)
>>> a
[1, 2, 3, 4]
>>> id(a) == id(b)
True

REPL makes the object model visible and tangible.

Debugging a Function in Isolation

>>> def process(data):
... return sorted(set(data))
...
>>> process([3, 1, 2, 1, 3])
[1, 2, 3]
>>> process([])
[]
>>> process(["c", "a", "b", "a"])
['a', 'b', 'c']

Test the function with multiple inputs without writing a test file.

Quick Math and Calculations

>>> import math
>>> math.sqrt(2)
1.4142135623730951
>>> math.pi * 10**2 ← area of circle, radius 10
314.1592653589793

AI/ML Real-World Connection

The REPL (and specifically Jupyter notebooks, which are powered by IPython's execution model) is the primary workspace for ML engineering:

# In a Jupyter cell - same REPL semantics:
import numpy as np
import torch

# Explore tensor behavior interactively
x = torch.randn(3, 3)
x

# Check dtype, device, shape
x.dtype, x.device, x.shape

# Verify gradient tracking
x.requires_grad_(True)
y = x @ x.T # matrix multiply
y.sum().backward()
x.grad # check gradients

Jupyter's cell-by-cell execution model is REPL with persistence and visualization. Every data scientist uses it. Understanding REPL fundamentals makes Jupyter intuitive.

Common Mistakes

Mistake 1: Testing Stale State

>>> model_weights = load_model() # loads something
>>> # 20 lines of experiments later...
>>> model_weights # has been modified during experiments

Always restart the kernel/REPL before final validation.

Mistake 2: Confusing Expression Output with print()

>>> [1, 2, 3] ← REPL shows this (expression result)
[1, 2, 3]

>>> print([1, 2, 3]) ← stdout write, print() returns None
[1, 2, 3]

Both display the same text, but the mechanism is different. In scripts, only print() displays output. The REPL's automatic display does not happen in .py files.

Mistake 3: Expecting Variables to Reset

>>> x = 10
>>> # close terminal, reopen, start new REPL session
>>> x
NameError: name 'x' is not defined

REPL state lives only as long as the session. There is no persistence between sessions.

Interview Questions

Q1: What does REPL stand for and what does each phase do?

Answer: Read-Eval-Print-Loop. Read: receives user input. Eval: compiles the input through Python's full pipeline (tokenize → parse → compile → execute via PVM). Print: displays the expression result if non-None. Loop: returns to the prompt for next input.

Q2: Does the REPL skip bytecode compilation?

Answer: No. Even in interactive mode, Python goes through the full compilation pipeline: tokenization, AST parsing, bytecode compilation, then PVM execution. The only difference from running a script is that this happens per-statement in real time instead of for the whole file upfront.

Q3: Why does assigning a variable in the REPL produce no output?

Answer: Assignment (x = 10) is a statement, not an expression. Statements do not return values (they return None implicitly). The REPL only displays non-None expression results. Since assignment returns None, nothing is displayed.

Q4: What is the _ variable in the REPL?

Answer: _ is automatically bound to the result of the last expression evaluated in the REPL session. It allows you to use the previous result without assigning it a name. For example: 100 * 200 produces 20000, and then _ + 1 gives 20001.

Q5: What is the difference between REPL and running a script?

Answer: REPL executes code interactively - one statement at a time, maintaining state, displaying expression results automatically, and waiting for more input. A script (python file.py) executes the entire file sequentially, exits when done, destroys all state, and requires explicit print() for output. Both use the same interpreter and compilation pipeline.

Q6: Why might code that works in REPL fail in a script?

Answer: REPL accumulates state across many lines and experiments. A script starts fresh with an empty namespace. If your code depends on a variable you set up in the REPL session but forgot to include in the script, the script fails. Always test final code in a clean script execution.

Quick Reference Cheatsheet

ActionREPL Command
Start REPLpython3
Exit REPLCtrl+D or exit()
Cancel incomplete inputCtrl+C
See last expression result_
Clear screenCtrl+L (macOS/Linux)
Start IPythonipython
Time a statement (IPython)%timeit statement
Run a script (IPython)%run script.py
List namespace (IPython)%who
Get help on object (IPython)object?

Graded Practice Challenges

Level 1 - Predict the Output

What does each line produce in the REPL?

>>> 5 * 5
>>> x = 5 * 5
>>> x
>>> print(x)
>>> _
Show Answer
25 ← expression, REPL displays
← assignment, no output (returns None)
25 ← expression (name lookup), REPL displays
25 ← print() writes to stdout as side effect
25 ← _ holds the last expression result (25 from line 4...
wait: print(x) returns None, so _ is None)
Actually: print(x) is an expression that returns None
So _ becomes None, and None is suppressed

Correction: after print(x), _ is None (print returns None). This is a subtle trap.

Level 2 - Debug the Session

A developer reports: "I tested my function in the REPL and it worked, but in my script it fails with NameError: name 'config' is not defined."

The function is:

def get_timeout():
return config["timeout"]

What is the most likely cause and fix?

Show Answer

The developer set config = {...} in the REPL session during testing. The function get_timeout() finds config in the REPL's global namespace. In the script, config was never defined - the REPL state is not present.

Fix: The script must define config before calling get_timeout(), or pass config as a parameter:

def get_timeout(config):
return config["timeout"]

Or define it at module level in the script.

Level 3 - Design Challenge

Design a workflow using the REPL for the following task: you have a CSV file and want to explore its structure, check data types, find missing values, and test a cleaning function before writing the final script.

Describe the exact sequence of REPL commands you would use.

Show Answer
>>> import pandas as pd

# Load and inspect
>>> df = pd.read_csv("data.csv")
>>> df.shape # rows, columns
>>> df.dtypes # column types
>>> df.head() # first 5 rows
>>> df.isnull().sum() # missing value counts

# Explore a specific column
>>> df['age'].describe()
>>> df['age'].unique()
>>> df['age'].value_counts()

# Test a cleaning function
>>> def clean_age(series):
... return series.fillna(series.median()).astype(int)
...
>>> cleaned = clean_age(df['age'])
>>> cleaned.isnull().sum() # should be 0
>>> cleaned.dtype # should be int64

# Test edge case
>>> clean_age(pd.Series([None, None, None])) # all missing

# Satisfied - now write to script

This workflow lets you verify each step interactively before committing to a script.

Key Takeaways

  • REPL stands for Read-Eval-Print-Loop - each phase has a specific job
  • Eval is full compilation - tokenize, parse, bytecode, PVM - not simplified interpretation
  • Expressions display their result in REPL; statements do not
  • _ stores the last expression result - use it for quick follow-up operations
  • State persists across all lines in a session - stale state is a real debugging hazard
  • Restart REPL when experiments become entangled or before final validation
  • IPython adds tab completion, magic commands, history, and documentation access
  • Jupyter notebooks run on IPython's execution model - same REPL semantics, visual output
  • REPL is an exploration and verification tool, not a replacement for writing scripts
  • Real engineers use the REPL daily - it is the fastest way to understand Python behavior
© 2026 EngineersOfAI. All rights reserved.