Jupyter Notebooks for Machine Learning
Why notebooks dominate ML experimentation
A Jupyter notebook is an interactive document that interleaves executable code cells, their outputs (printed values, tables, plots, images), and narrative text in a single file, run against a persistent backend process called a kernel. Unlike a script, which runs top to bottom and exits, a notebook keeps the kernel's state alive between cell executions — variables, loaded data, and trained models stay in memory while you keep working.
This interactivity is exactly what machine learning experimentation needs. Loading a large dataset or training a model can take minutes; in a notebook, you pay that cost once and then iterate rapidly — try a different plot, inspect a different slice of predictions, tweak one hyperparameter — without re-running everything from scratch. Immediate inline visualization of data distributions, training curves, and confusion matrices, right next to the code that produced them, makes notebooks a natural fit for the exploratory, iterative nature of ML work.
Note
The typical notebook-driven ML workflow
A common pattern: load and inspect raw data in early cells, visualize distributions and relationships, clean and engineer features incrementally — rerunning individual cells to check each transformation's effect — then train a baseline model, inspect its errors, and iterate on features or hyperparameters cell by cell. Markdown cells interspersed with code document reasoning and findings as you go, making the notebook double as a lab notebook: both the experiment and its record live in the same file.
This tight feedback loop (change one thing, immediately see its effect, without re-executing the entire pipeline) is why notebooks remain the default starting point for data exploration and model prototyping across the industry, even among teams that are strict about production code quality elsewhere.
Pitfall: hidden state
Because the kernel persists across cell executions, a notebook's visible code (top to bottom) can diverge silently from its actual execution history. If you run cell 5, then go back and edit cell 2, then re-run cell 5 without re-running cell 2, cell 5 executes against the *old* state from before your edit — the notebook's displayed order no longer matches what actually happened. Variables can be defined in a cell you later deleted, or modified out of order, leaving state in memory that the visible notebook file gives no evidence of.
This hidden state problem is the single most common source of "it worked for me" bugs in notebooks — a notebook that runs perfectly cell-by-cell in whatever order you happened to execute them can fail completely, or silently produce different results, when run fresh from top to bottom.
Note
Pitfall: non-reproducibility
A notebook that looks complete can still be impossible for someone else (or you, in six months) to reproduce. Common causes: non-deterministic operations without a fixed random seed (so a model trains differently each run), cells that depend on external files, databases, or API calls with values that have since changed, outputs saved from a run with code that no longer matches what's currently in the cells, and dependency versions that aren't pinned, so a library update silently changes behavior.
Notebooks also complicate standard software practices: diffing notebook files in version control is noisy because the underlying JSON format stores outputs and execution counts alongside code, making code review harder than it would be for a plain .py file, and meaningful unit testing of notebook cells is awkward compared to testing functions in a module.
Practical mitigations
Several habits substantially reduce notebook risk: restart-and-run-all before considering any result trustworthy or before sharing a notebook; set random seeds explicitly wherever randomness is involved; pin dependency versions (a requirements.txt or environment file alongside the notebook); keep cells focused and avoid deeply interleaved, order-dependent logic; and move code that's proven out — reusable feature transforms, model training functions, evaluation utilities — into a separate .py module that the notebook imports, rather than letting that logic live only as notebook cells. This last habit also makes the proven code immediately testable and reusable outside the notebook once it's ready for a pipeline or production system.
Notebooks vs. scripts for ML work
| Aspect | Notebooks | Scripts (.py) |
|---|---|---|
| Best for | Exploration, visualization, iterative prototyping | Reproducible pipelines, production, scheduled jobs |
| State management | Persistent kernel — fast iteration, risk of hidden state | Runs clean top to bottom every time |
| Version control diffs | Noisy (JSON format, embedded outputs) | Clean, line-based diffs |
| Testability | Awkward — cells aren't functions | Straightforward — import and unit test |
| Typical role in workflow | Where ideas are explored and validated first | Where validated logic ends up for reliable execution |
What's next
A good notebook habit is also good practice for every other topic in this track: validate your results survive a clean, reproducible run before trusting them. With the core ML workflow and tooling covered across this track, the next step is applying these ideas end-to-end on a real dataset of your choosing — the concepts covered so far (regularization, tuning, imbalanced data, ensembling, calibration) are the toolkit; practice is what turns them into intuition.
I build these systems professionally.
Whether it's a RAG pipeline, analytics migration, or AI workflow — let's talk.