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

bash
# JupyterLab (recommended)
pip install jupyterlab

# Classic Notebook interface
pip install notebook

# Both
pip install jupyterlab notebook

Output: (none — exits 0 on success)

Launch

bash
jupyter lab           # opens JupyterLab at http://localhost:8888
jupyter notebook      # opens classic Notebook interface
jupyter lab --no-browser --port 8889   # headless / custom port

Output:

text
[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 .py module.
  • 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.ipynb files contain cell outputs (images, dataframes) in JSON, making diffs huge. Use nbstripout to 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):

KeyAction
EnterSwitch to edit mode
Shift+EnterRun cell, move to next
Ctrl+EnterRun cell, stay
Alt+EnterRun cell, insert below
AInsert cell above
BInsert cell below
DDDelete cell
ZUndo cell deletion
MConvert cell to Markdown
YConvert cell to Code
LToggle line numbers
IIInterrupt kernel
00Restart kernel
Shift+MMerge 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.

python
# Time a single expression
%time sum(range(1_000_000))

Output:

text
CPU times: user 23.4 ms, sys: 0 ns, total: 23.4 ms
Wall time: 23.4 ms
python
# Benchmark over many runs
%%timeit
total = 0
for i in range(1_000_000):
    total += i

Output:

text
28.5 ms ± 312 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
python
%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

python
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:

text
[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).

bash
# 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:

text
[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.

bash
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:

text
Available kernels:
  python3    /usr/local/share/jupyter/kernels/python3
  myenv      /home/user/.local/share/jupyter/kernels/myenv

Always install ipykernel inside the virtual environment you want to use, then register it with python -m ipykernel install --user --name <name>. This makes the venv selectable from the JupyterLab kernel picker.

Strip outputs before committing

bash
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-endStrengthsWhen to pick it
JupyterLabMulti-tab, file browser, terminals, extensions, debug UIDefault for most data-science work in 2026
Classic NotebookSingle-document, fast startup, simple UIQuick scripts, demos, teaching
VS CodeIntegrated source control, breakpoints, inline AIMixed notebook + .py workflows
Cursor / PositronBuilt on VS Code with AI/data featuresModern AI-pair-programming notebooks
marimoReactive cells (auto-rerun dependents), .py storageReproducibility, dashboards from notebooks
Quarto / ObservableStatic publishing, multi-languageReports, 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.

python
# 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
python
# Profile a cell line by line (requires line_profiler)
%load_ext line_profiler
%%lprun -f my_function
my_function(big_array)
python
# Run shell commands and capture output to a Python variable
files = !ls *.csv
print(files)
python
# Write the cell contents to a file
%%writefile module.py
def greet(name):
    return f"Hello, {name}"
python
# Run cell as a different language
%%bash
echo "current dir:"
pwd
ls *.py | head
python
# Render the cell as Markdown / HTML / SVG / LaTeX
%%html
<div style="padding: 1em; background: #f0f0f0;">
  <strong>Custom HTML output</strong>
</div>
python
# 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.

python
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]}))
python
# 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.

python
# 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).

bash
pip install papermill

Output: (none — exits 0 on success)

In the notebook, tag a cell parameters:

python
# Cell tagged "parameters"
month = "2026-01"
threshold = 100

Run it from the shell:

bash
papermill report.ipynb output_2026_01.ipynb -p month "2026-01" -p threshold 100

Output:

text
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:

python
import papermill as pm

pm.execute_notebook(
    "report.ipynb",
    "output.ipynb",
    parameters={"month": "2026-01", "threshold": 100},
)

Pair papermill with nbconvert --to html to 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).

bash
# 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:

text
[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.

bash
pip install voila
voila dashboard.ipynb --no-browser --port 8866
# → opens http://localhost:8866 with the notebook as a clean web app

Output:

text
[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 .py script, 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.

bash
# 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:

text
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:

bash
cat ~/.local/share/jupyter/kernels/myproject/kernel.json

Output:

json
{
  "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 python3 with nbconvert --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:

python
# 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.

bash
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:

text
'/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:

bash
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:

text
[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:

python
# %% 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.

bash
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:

text
Writing default config to: /home/alice/.jupyter/jupyter_lab_config.py

Run with a token-free password:

bash
jupyter lab password           # prompts for password, stores hash
jupyter lab                    # now requires password instead of token

Output:

text
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:

bash
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 %%time to spot slow cells, and consider KernelInterrupt (II) before the kernel runs out of memory.

pip install from 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

python
%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.

bash
#!/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:

bash
jupyter nbconvert --to markdown analysis.ipynb \
    --NbConvertApp.output_files_dir=./img

Output:

text
[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

python
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.

python
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 inline for 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.