Command Palette

Search for a command to run...

Hectal
PHASE 16Advanced ~25 min· topic 2 of 14

Topic 16.2

Jupyter Notebooks and the Experiment Workflow

In one line

A notebook is a document of code cells that share one running Python process, which makes it excellent for exploring data and risky for anything you need to repeat. The workflow is to explore in a notebook, then move the working code into modules with tests, seeds and locked dependencies.

Think of it like this

A lab notebook on a workbench. You try a mixture, jot the result next to it, try another. It is perfect for discovering things. But if you want someone else to make the same mixture next month, you do not hand them the messy notebook. You write a clean recipe. A Jupyter notebook is the lab notebook; a Python module is the recipe.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Notebook
A document made of code cells and text cells whose outputs are saved inside it.
Cell
One block of code or text in a notebook. You run it on its own.
Kernel
The running Python process behind a notebook. Its memory is shared by every cell.
Hidden state
Values still alive in the kernel that no visible cell order would create.
Seed
A starting number for a random number generator. The same seed gives the same "random" numbers.
Reproducible
Another run gets the same result.
Lock file
A file listing the exact version of every dependency, so installs match.

Step by step

01A notebook is a shared dictionary

Under the hood, every cell runs in the same namespace, like a single dictionary of variables. The first example below builds a toy kernel so you can see it. It uses the built-in exec, which runs a piece of text as Python code. You will rarely need exec yourself. Here it stands in for "run this cell". Running a cell never clears what earlier cells left behind.

02The hidden-state trap

Suppose cell 1 sets price = 100, cell 2 prints it, and cell 3 computes a discount. You change cell 1 to 500 and re-run only cell 1. Cell 2's old output on screen still says 100 until you run it. Worse, if you delete cell 1 entirely, the kernel still remembers price, so everything keeps working until the day you restart.

03Restart and run all

The fix is mechanical: throw the memory away and run every cell in order. In a real notebook this is a menu item. In the toy kernel it is creating a new Kernel() and replaying the cells. If the result differs, your notebook depended on hidden state.

main.pywhole filepython
def run_in_order(cells):
    fresh = Kernel()       # "Restart kernel and run all"
    for code in cells:
        fresh.run_cell(code)
    return fresh.state

04Moving code into a module

When a cell's code works, cut it into a function in a .py file. Then the notebook says from experiment import noisy_scores. Now the logic can be imported, tested with pytest (Phase 11) and reviewed in a normal diff. The second example writes a module and imports it, exactly as you would from a notebook.

05Seeds and pinned versions

Random numbers in Python come from a generator that starts at a seed. Give it the same seed and you get the same stream. Recall from Topic 14.4: NumPy's modern way is np.random.default_rng(seed). The standard library's is random.Random(seed). Pass the seed in as a parameter instead of setting a hidden global, so the notebook cell and the tests use the same one.

For dependencies, write exact versions in a lock file (uv.lock, or a pinned requirements.txt). A notebook that worked last spring and fails today is very often a library that updated.

requirements.txtwhole filetext
numpy==2.2.0
pandas==2.3.0

06Notebooks in the AI workflow

With LLMs, a notebook is a good place to try a prompt (the text you send to the model) on ten examples and read the answers. Once a prompt works, move it into a template (Topic 16.7), the call into a function (Topic 16.3), and add tests with a fake model (Topic 16.11). Keep API keys out of notebooks: read them from environment variables, because notebooks get shared, committed and screenshotted.

Try it yourself

  1. 1

    Break the order

    In the first example, swap two of the run_cell lines so a cell uses a name before it exists. Predict the error first, then run.

  2. 2

    Prove reproducibility

    In the second example change the seed from 7 to 8 in the first call and compare. Then put it back and confirm run 1 and run 2 match.

Code & diagrams

A toy kernel shows the hidden-state trap New tab

Read the comments, predict each printed line, then run it.

Sign in to run this example in your browser.

Expected output

cell 2: price is 100
cell 2 again: 500 -> discounted 450.0
discounted is still 450.0 but price is 50
A module with seeds, imported from the "notebook" New tab

Same seed, same numbers. A different seed gives different numbers.

Sign in to run this example in your browser.

Expected output

run 1: [70.006, 71.494, 68.629, 65.547]
run 2: [70.006, 71.494, 68.629, 65.547]
other seed: [61.309, 63.317, 63.194, 68.242]
shuffled: ['d', 'a', 'c', 'b']
The notebook workflowdiagram
Rendering diagram…

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Run a cell before its dependency

Use a name that an earlier cell was supposed to define, but that was never run.

main.pywhole filepython
data = [3, 5, 8]
print("average:", sum(data) / len(data))
print("largest:", biggest)
terminal
$ python main.py
── what you'll see ──
average: 5.333333333333333
Traceback (most recent call last):
File "main.py", line 3, in <module>
print("largest:", biggest)
^^^^^^^
NameError: name 'biggest' is not defined

Myth vs fact

Myth

A notebook that shows the right output is correct.

Fact

The output may be left over from an earlier run or from a deleted cell. Only a clean restart and run-all proves it.

Myth

Notebooks are only for beginners.

Fact

Researchers and senior engineers use them daily for exploration. The skill is knowing when to leave the notebook and move code into tested modules.

Myth

Setting a seed makes everything reproducible.

Fact

A seed covers one random generator. Library versions, data changes, hardware differences (GPU arithmetic) and hosted model updates can still change results.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A .ipynb file is JSON that stores code, outputs and metadata. Outputs and execution counters create noisy diffs. Teams strip outputs before committing (with tools such as nbstripout) or pair notebooks with plain Python files, so code review stays readable.

  • ▸

    Tools that run notebooks as tests exist (executing every cell in a clean kernel in CI), which turns "restart and run all" into an automatic check. Pair it with a pinned lock file so CI and your machine agree.

  • ▸

    Seeds interact with parallelism. Splitting work across processes needs separate child seeds (NumPy's SeedSequence is built for that), otherwise workers may repeat the same stream.

Remember this

  1. 1

    A notebook (a .ipynb file) is a list of cells: some hold code, some hold text. A kernel is the Python process that runs the code cells. All cells share the kernel's memory, so a variable defined in one cell is visible in the next. The output of a cell (text, tables, charts) is saved right under it.

  2. 2

    The central trap is hidden state. You can run cells in any order, edit a cell and not re-run the ones below it, or delete a cell whose variable still lives in the kernel. The notebook on screen then shows results that no top-to-bottom run would produce. Two people with the same file can get different answers.

  3. 3

    The cure is a habit: Restart the kernel and run all cells before you trust or share a result. If it fails or the numbers change, the notebook was lying. Also keep cells small, give them clear order, and put real logic in functions.

  4. 4

    Notebooks shine for exploring a dataset, drawing quick charts, trying a prompt and looking at the output, and teaching. They are weak for code that must be tested, imported by other code, reviewed line by line in version control (the saved JSON is noisy), or run on a schedule.

  5. 5

    The workflow professionals use: explore in a notebook, extract every function that works into a .py module, import it back into the notebook, and test the module. The notebook becomes a thin report on top of tested code. VS Code notebooks and Google Colab are two common places to run notebooks; Colab also gives access to a GPU.

  6. 6

    Reproducibility means someone else (or you in six months) gets the same result. Three habits give most of it: set seeds for every random number generator, pin dependency versions in a lock file (Phase 6), and record the data version you used. AI work adds one more. A hosted model is a model that runs on a company's servers, and you call it over the internet. The company can update it, so log which model name you called.

Explain it without notes

01

What is a kernel, and why does it cause the hidden-state problem?

02

How do you make a notebook result trustworthy and shareable?

03

Describe a workflow that takes an experiment from a notebook to production code.

04

Two data scientists run the same notebook and get different numbers. List the causes you would check, in order.

Practice

01

Write a function run_in_order(cells) that runs a list of code strings in a fresh shared namespace and returns the namespace.

02

Write a function sample(seed) that returns five dice rolls from its own seeded generator, and show that equal seeds match.

03

Write a function to_requirements(reqs) that turns a dict of name to version into sorted pinned lines.

Trade-offs

  • ↔

    Notebooks trade repeatability for speed of exploration. You see results instantly and keep the narrative with the code. The price is hidden state and poor fit for testing and review.

  • ↔

    Moving code out costs time now and saves it later. Extracting modules feels slower on day one. It is what makes the work testable, reusable and safe to hand to a teammate.

Done when you can

  • Done when you can explain a kernel and cells in your own words.

  • Done when you can give an example of hidden state and fix it with restart and run-all.

  • Done when you can seed both random and NumPy generators and show two runs match.

  • Done when you can move a working cell into a module and import it.

  • Done when you know why API keys do not belong inside notebooks.