cheat sheet
jupyter
Run interactive Python notebooks with Jupyter. Covers JupyterLab setup, cell types, keyboard shortcuts, magic commands, nbconvert export, and common pitfalls.
jupyter — Interactive Notebooks
What it is
Jupyter notebooks (.ipynb files) let you mix executable Python cells, markdown, and rich outputs (plots, tables, HTML) in a single document. There are two interfaces:
- JupyterLab — modern IDE-like interface (recommended)
- Jupyter Notebook — classic single-document interface
Both run a local web server and open in your browser. Notebooks are widely used for data exploration, prototyping, and technical writing.
Install
# JupyterLab (recommended)
pip install jupyterlab
# Classic Notebook interface
pip install notebook
# Both
pip install jupyterlab notebook
Output: (none — exits 0 on success)
Launch
jupyter lab # opens JupyterLab at http://localhost:8888
jupyter notebook # opens classic Notebook interface
jupyter lab --no-browser --port 8889 # headless / custom port
Output:
[I 2026-04-25 14:30:01.234 ServerApp] JupyterLab 4.2.0 is running at:
[I 2026-04-25 14:30:01.234 ServerApp] http://localhost:8888/lab?token=abc123...
[I 2026-04-25 14:30:01.234 ServerApp] or http://127.0.0.1:8888/lab?token=abc123...
[I 2026-04-25 14:30:01.234 ServerApp] Use Control-C to stop this server...
Your browser opens automatically. If it doesn't, copy the URL from the terminal output.
When / why to use it
- Exploratory data analysis where you want to see output inline (plots, DataFrames).
- Prototyping an algorithm step by step before putting it in a
.pymodule. - Writing a technical document or report that mixes prose and code.
- Teaching or presenting code — cells can be run one at a time.
Common pitfalls
Execution order is not document order — cells can be run in any order. A notebook that runs top-to-bottom passes CI; one where you ran cells out of order may have hidden state. Always use Kernel → Restart & Run All before sharing to verify the notebook runs cleanly from scratch.
Large notebooks in version control —
.ipynbfiles contain cell outputs (images, dataframes) in JSON, making diffs huge. Usenbstripoutto strip outputs before committing:pip install nbstripout && nbstripout --install.
Variables set in one cell persist for the rest of the session. If you delete or change a cell, the old value stays in memory until you restart the kernel.
Keyboard shortcuts
These work in command mode (press Esc to enter, highlighted cell border turns blue):
| Key | Action |
|---|---|
Enter | Switch to edit mode |
Shift+Enter | Run cell, move to next |
Ctrl+Enter | Run cell, stay |
Alt+Enter | Run cell, insert below |
A | Insert cell above |
B | Insert cell below |
DD | Delete cell |
Z | Undo cell deletion |
M | Convert cell to Markdown |
Y | Convert cell to Code |
L | Toggle line numbers |
II | Interrupt kernel |
00 | Restart kernel |
Shift+M | Merge selected cells |
In edit mode (green border): standard text editor shortcuts apply, plus Tab for autocomplete and Shift+Tab for docstring pop-up.
Magic commands
Magic commands start with % (line magic) or %% (cell magic). They run before your Python code is evaluated.
# Time a single expression
%time sum(range(1_000_000))
Output:
CPU times: user 23.4 ms, sys: 0 ns, total: 23.4 ms
Wall time: 23.4 ms
# Benchmark over many runs
%%timeit
total = 0
for i in range(1_000_000):
total += i
Output:
28.5 ms ± 312 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%matplotlib inline # render matplotlib plots inline in the notebook
%who # list all variables in scope
%whos # list variables with type and value
%env PATH # show/set environment variable
%run script.py # run a .py file and import its globals
%load utils.py # load a file's contents into the current cell
%pwd # print working directory
%ls # list directory (like shell ls)
Richer example — inline plotting
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
x = np.linspace(0, 4 * np.pi, 500)
fig, ax = plt.subplots(figsize=(9, 3))
ax.plot(x, np.sin(x), label="sin(x)")
ax.plot(x, np.exp(-x / 5) * np.sin(x), label="damped sin(x)", linestyle="--")
ax.set_title("Waveforms")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
Output:
[inline figure rendered in the notebook cell output — a waveform plot with two curves]
Export / convert notebooks
jupyter nbconvert transforms a .ipynb file into another format without opening the browser interface. Convert to HTML for sharing, to a .py script for code review or CI, or to PDF for formal reports (PDF requires a LaTeX installation).
# To HTML (share as a static page)
jupyter nbconvert --to html analysis.ipynb
# To Python script (strip all outputs and markdown, keep code cells)
jupyter nbconvert --to script analysis.ipynb
# To PDF (requires LaTeX)
jupyter nbconvert --to pdf analysis.ipynb
# To Markdown
jupyter nbconvert --to markdown analysis.ipynb
Output:
[NbConvertApp] Converting notebook analysis.ipynb to html
[NbConvertApp] Writing 312345 bytes to analysis.html
List and manage kernels
Each Jupyter kernel is an independent Python environment. jupyter kernelspec list shows all registered kernels; install ipykernel inside a virtual environment and run python -m ipykernel install to make that venv available as a selectable kernel in JupyterLab.
jupyter kernelspec list # list installed kernels
jupyter kernelspec remove myenv # remove a kernel
pip install ipykernel # install ipykernel to register a venv
python -m ipykernel install --user --name myenv --display-name "Python (myenv)"
Output of kernelspec list:
Available kernels:
python3 /usr/local/share/jupyter/kernels/python3
myenv /home/user/.local/share/jupyter/kernels/myenv
Always install
ipykernelinside the virtual environment you want to use, then register it withpython -m ipykernel install --user --name <name>. This makes the venv selectable from the JupyterLab kernel picker.
Strip outputs before committing
pip install nbstripout
nbstripout --install # installs a git filter for this repo
# From now on: git diff and git add see clean notebooks (no outputs)
Output: (none — exits 0 on success)
JupyterLab vs classic Notebook vs alternatives
The Jupyter ecosystem has converged on multiple front-ends that all execute the same .ipynb files via the same kernel protocol. Pick the front-end based on workflow, not capability.
| Front-end | Strengths | When to pick it |
|---|---|---|
| JupyterLab | Multi-tab, file browser, terminals, extensions, debug UI | Default for most data-science work in 2026 |
| Classic Notebook | Single-document, fast startup, simple UI | Quick scripts, demos, teaching |
| VS Code | Integrated source control, breakpoints, inline AI | Mixed notebook + .py workflows |
| Cursor / Positron | Built on VS Code with AI/data features | Modern AI-pair-programming notebooks |
| marimo | Reactive cells (auto-rerun dependents), .py storage | Reproducibility, dashboards from notebooks |
| Quarto / Observable | Static publishing, multi-language | Reports, technical blogs |
If notebook reproducibility bites you regularly, consider
marimo— cells reactively rerun their downstream dependents, eliminating the "ran cells out of order" pitfall.
Cell magics in depth
Cell magics (%%) operate on the entire cell body and run before the cell is parsed as Python. They are some of the highest-leverage Jupyter features.
# Time a block over many runs (statistical, like %timeit but for blocks)
%%timeit -n 100 -r 5
total = 0
for i in range(10_000):
total += i * i
# Profile a cell line by line (requires line_profiler)
%load_ext line_profiler
%%lprun -f my_function
my_function(big_array)
# Run shell commands and capture output to a Python variable
files = !ls *.csv
print(files)
# Write the cell contents to a file
%%writefile module.py
def greet(name):
return f"Hello, {name}"
# Run cell as a different language
%%bash
echo "current dir:"
pwd
ls *.py | head
# Render the cell as Markdown / HTML / SVG / LaTeX
%%html
<div style="padding: 1em; background: #f0f0f0;">
<strong>Custom HTML output</strong>
</div>
# Capture stdout/stderr from a cell into a variable
%%capture out
print("hello")
import warnings
warnings.warn("oops")
# out.stdout, out.stderr, out.outputs available below
Display API and rich outputs
The IPython.display module turns Python objects into rich notebook outputs. Use display(obj) instead of print() when you want HTML, tables, images, audio, or video; combine with clear_output(wait=True) for live-updating progress displays.
from IPython.display import display, HTML, Markdown, Image, JSON, clear_output
import pandas as pd
display(HTML("<h3>Section header</h3>"))
display(Markdown("**bold** and `code`"))
display(pd.DataFrame({"a": [1, 2], "b": [3, 4]}))
display(Image(filename="diagram.png"))
display(JSON({"key": "value", "nested": [1, 2, 3]}))
# Live-updating progress
import time
for i in range(5):
clear_output(wait=True)
print(f"step {i+1}/5")
time.sleep(0.5)
Widgets — ipywidgets and interact
ipywidgets gives notebooks interactive UI controls (sliders, dropdowns, buttons) that re-execute a function on change. @interact is the one-line decorator; for richer dashboards, compose widgets explicitly and link them with observe.
# pip install ipywidgets
from ipywidgets import interact, IntSlider
import matplotlib.pyplot as plt
import numpy as np
@interact(freq=IntSlider(min=1, max=10, value=3))
def plot_sine(freq):
x = np.linspace(0, 2*np.pi, 200)
plt.figure(figsize=(6, 2))
plt.plot(x, np.sin(freq * x))
plt.title(f"sin({freq}x)")
plt.show()
Parametrise notebooks with papermill
papermill runs a notebook with substituted parameters from the command line or another Python script. The notebook flags a cell with the parameters tag; papermill injects a new cell after it with the supplied values, then executes the whole document. This is the standard way to use notebooks as parametric reports in pipelines (e.g. Airflow, Dagster).
pip install papermill
Output: (none — exits 0 on success)
In the notebook, tag a cell parameters:
# Cell tagged "parameters"
month = "2026-01"
threshold = 100
Run it from the shell:
papermill report.ipynb output_2026_01.ipynb -p month "2026-01" -p threshold 100
Output:
Input Notebook: report.ipynb
Output Notebook: output_2026_01.ipynb
Executing: 0%| | 0/12 [00:00<?, ?cell/s]
Executing notebook with kernel: python3
Executing: 100%|██████████| 12/12 [00:08<00:00, 1.42cell/s]
Or from Python:
import papermill as pm
pm.execute_notebook(
"report.ipynb",
"output.ipynb",
parameters={"month": "2026-01", "threshold": 100},
)
Pair
papermillwithnbconvert --to htmlto generate parameterised HTML reports for every customer/region/period in a loop.
nbconvert in detail
nbconvert is more than HTML export — it has templates that control every detail of the output. The --template flag picks a template; common ones are lab (default HTML with JupyterLab styling), classic, latex_index, and webpdf (rendered via Chromium without LaTeX).
# Export to HTML without code cells (presentation mode)
jupyter nbconvert --to html --no-input report.ipynb
# Export to slides (reveal.js)
jupyter nbconvert --to slides analysis.ipynb --post serve
# Execute then export (run cells first)
jupyter nbconvert --to html --execute --ExecutePreprocessor.timeout=120 report.ipynb
# Convert and tag the kernel for reproducibility
jupyter nbconvert --to notebook --execute --output executed.ipynb input.ipynb
# Render via headless Chromium (no LaTeX needed)
jupyter nbconvert --to webpdf --allow-chromium-download report.ipynb
Output:
[NbConvertApp] Converting notebook report.ipynb to html
[NbConvertApp] Writing 412034 bytes to report.html
[NbConvertApp] Converting notebook analysis.ipynb to slides
[NbConvertApp] Writing 287512 bytes to analysis.slides.html
[NbConvertApp] Executing notebook with kernel: python3
[NbConvertApp] Writing 654321 bytes to executed.ipynb
[NbConvertApp] Building PDF
[NbConvertApp] PDF successfully created
Voilà — turn a notebook into a web app
voilà strips out the code cells and serves the remaining outputs and widgets as a standalone web app. Great for sharing notebook-based dashboards with non-technical viewers — they see only the rendered outputs and any interactive widgets.
pip install voila
voila dashboard.ipynb --no-browser --port 8866
# → opens http://localhost:8866 with the notebook as a clean web app
Output:
[Voila] Voilà is running at: http://localhost:8866/
[Voila] Connecting to kernel
[Voila] Kernel started: 8d2f4c9e-1a3b-4e5f-9c8d-7a6b5c4d3e2f
For a more polished UI consider streamlit — it is a separate tool (writes a
.pyscript, not a notebook), but produces a similar "interactive Python app" deliverable.
Kernel management deep dive
Each kernel registration is a JSON file in ~/.local/share/jupyter/kernels/<name>/kernel.json describing the command to launch the interpreter, environment variables, display name, and language. Install a kernel for a virtual environment so Jupyter can find its packages.
# Activate the venv first
source .venv/bin/activate
pip install ipykernel
# Register the venv
python -m ipykernel install \
--user \
--name myproject \
--display-name "Python (myproject)"
# Verify
jupyter kernelspec list
Output:
Installed kernelspec myproject in /home/alice/.local/share/jupyter/kernels/myproject
Available kernels:
myproject /home/alice/.local/share/jupyter/kernels/myproject
python3 /home/alice/.local/share/jupyter/kernels/python3
Inspect a kernel spec:
cat ~/.local/share/jupyter/kernels/myproject/kernel.json
Output:
{
"argv": [
"/home/alice/projects/myproject/.venv/bin/python",
"-m", "ipykernel_launcher",
"-f", "{connection_file}"
],
"display_name": "Python (myproject)",
"language": "python"
}
If a notebook works locally but fails on a colleague's machine with
ModuleNotFoundError, it is almost always the wrong kernel. Pin the kernel name in the notebook metadata (or use--kernel python3withnbconvert --execute) so the environment is explicit.
Debugging a notebook
JupyterLab has an integrated debugger; enable the bug icon in the right sidebar. For programmatic debugging:
# Drop into pdb at the next exception
%pdb on
# Step through a specific call
def buggy():
x = 1
y = 0
return x / y
%debug
buggy() # opens pdb post-mortem on the ZeroDivisionError
Useful pdb commands inside %debug: n (next), s (step into), l (list source), p var (print), c (continue), q (quit).
nbstripout and git workflow
Jupyter notebooks store cell outputs (large base64 images, multi-megabyte DataFrames) directly in the .ipynb JSON, which inflates diffs and blows up repo size. nbstripout installs a git filter that strips outputs on commit but keeps them in your working copy.
pip install nbstripout
# Per-repo (recommended)
nbstripout --install
# Per-user (applies globally)
nbstripout --install --global
# Verify the filter is wired
git config filter.nbstripout.clean
Output:
'/path/to/.venv/bin/python' -m nbstripout
For higher-stakes notebooks where you do want outputs in git (e.g. teaching material), use Git LFS for large embedded media instead of nbstripout.
Notebook to script — clean conversion
nbconvert --to script keeps every code cell and strips outputs, but the result is rarely clean enough to commit as a module. For production code, do the conversion once with jupytext and pair the .py with the .ipynb:
pip install jupytext
# Pair an existing notebook with a percent-format .py
jupytext --set-formats ipynb,py:percent analysis.ipynb
# Now editing either file syncs to the other
jupytext --sync analysis.ipynb
Output:
[jupytext] Reading analysis.ipynb in format ipynb
[jupytext] Updating notebook metadata with '{"jupytext": {"formats": "ipynb,py:percent"}}'
[jupytext] Updating analysis.ipynb
[jupytext] Updating analysis.py
[jupytext] Loading analysis.ipynb
[jupytext] analysis.py is up to date with analysis.ipynb
The .py percent-format file uses # %% separators that JupyterLab and VS Code both recognise as cell boundaries:
# %% Cell 1
import pandas as pd
df = pd.read_csv("data.csv")
# %% [markdown]
# ## Analysis
# %% Cell 2
df.describe()
Server configuration
Generate a config file and edit it to expose Jupyter on a specific port, bind to all interfaces (for a remote VM), or set a password.
jupyter lab --generate-config
# Edit ~/.jupyter/jupyter_lab_config.py
# c.ServerApp.ip = "0.0.0.0"
# c.ServerApp.port = 8888
# c.ServerApp.open_browser = False
# c.ServerApp.password = "..." # generate with jupyter lab password
Output:
Writing default config to: /home/alice/.jupyter/jupyter_lab_config.py
Run with a token-free password:
jupyter lab password # prompts for password, stores hash
jupyter lab # now requires password instead of token
Output:
Enter password:
Verify password:
[JupyterPasswordApp] Wrote hashed password to /home/alice/.jupyter/jupyter_server_config.json
[I 2026-05-25 09:14:02.123 ServerApp] JupyterLab application directory is /home/alice/.local/share/jupyter/lab
[I 2026-05-25 09:14:02.456 ServerApp] Jupyter Server 2.14.0 is running at:
[I 2026-05-25 09:14:02.456 ServerApp] http://localhost:8888/lab
Never expose a Jupyter server on a public IP without
--ip=127.0.0.1+ an SSH tunnel, or strong authentication. The kernel runs arbitrary code; an unauthenticated server is a full shell.
For remote access via SSH tunnel:
ssh -L 8888:localhost:8888 alice@myhost
# then on the remote: jupyter lab --no-browser
# locally: open http://localhost:8888
Output: (none — exits 0 on success)
Common pitfalls (reference)
Hidden state from out-of-order execution — variables can outlive the cell that created them. Always Kernel → Restart & Run All before assuming the notebook is correct.
Plots not showing after
%matplotlib inline— if you set the backend after importing pyplot, the change does not take effect. Set magic at the top of the notebook.
Long-running cells hang the kernel — there is no time-out by default. Use
%%timeto spot slow cells, and considerKernelInterrupt(II) before the kernel runs out of memory.
pip installfrom inside a notebook installs into the wrong env — use%pip install pkg(the magic), not!pip install pkg. The magic resolves to the kernel's interpreter.
Large outputs make notebooks impossible to open — accidentally printing a 10M-row DataFrame fills the JSON. Use
df.head()and clear outputs before saving.
Real-world recipes
Quick exploration of a CSV
%pip install -q pandas matplotlib
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
df = pd.read_csv("sales.csv", parse_dates=["date"])
display(df.head())
display(df.describe())
df.set_index("date")["amount"].resample("W").sum().plot(figsize=(9, 3), title="weekly sales");
Run a notebook as a daily report
Combine papermill + nbconvert + cron / Airflow.
#!/usr/bin/env bash
# /etc/cron.daily/report.sh
DATE=$(date +%Y-%m-%d)
papermill /home/alice/reports/daily.ipynb /tmp/daily_${DATE}.ipynb \
-p run_date "$DATE"
jupyter nbconvert --to html --no-input /tmp/daily_${DATE}.ipynb \
--output /var/www/reports/daily_${DATE}.html
Output: (none — exits 0 on success)
Embed a notebook in a docs site
Convert to Markdown with images stripped to a separate dir:
jupyter nbconvert --to markdown analysis.ipynb \
--NbConvertApp.output_files_dir=./img
Output:
[NbConvertApp] Converting notebook analysis.ipynb to markdown
[NbConvertApp] Support files will be in img/
[NbConvertApp] Writing 28471 bytes to analysis.md
The resulting analysis.md is drop-in for any static site generator (Astro, Hugo, MkDocs).
Pair with tqdm for progress bars
from tqdm.auto import tqdm # auto-selects notebook widget when in Jupyter
import time
total = 0
for i in tqdm(range(100), desc="processing"):
total += i
time.sleep(0.02)
Cache expensive cells with joblib.Memory
A pure-Python alternative to manual pickling — caches function calls keyed by arguments.
from joblib import Memory
memory = Memory(".cache", verbose=0)
@memory.cache
def fetch_and_clean(date):
print(f"running expensive job for {date}")
# ... fetch data, clean, return DataFrame
return f"result for {date}"
print(fetch_and_clean("2026-01")) # runs and caches
print(fetch_and_clean("2026-01")) # cache hit, no print
See also
- pandas — DataFrames render as rich HTML tables inline.
- matplotlib —
%matplotlib inlinefor inline plots. - numpy — array math, the underlying numeric layer.
- streamlit — alternative for delivering interactive Python apps.
- scikit-learn — ML pipelines that pair naturally with notebooks.