Marimo vs Jupyter Notebooks in 2026: Reactive Python Notebooks Compared

Marimo replaces Jupyter's manual execution with a reactive DAG, plain Python files, and one-command WASM deploys. Here's how the two notebooks compare in 2026.

Marimo vs Jupyter 2026: Reactive Python Guide

Updated: May 26, 2026

Marimo is a reactive Python notebook that stores files as plain .py scripts and automatically re-runs dependent cells whenever upstream code or UI inputs change, killing the hidden-state bugs that plague Jupyter notebooks. In 2026, Marimo crossed 20,000 GitHub stars and shipped marimo pair, an agent-native session mode that lets Claude Code and Codex drive a live notebook. This guide compares Marimo vs Jupyter on execution model, file format, reproducibility, deployment, and AI tooling so you can decide whether to switch your Python data science workflow today.

  • Marimo executes cells as a dependency DAG. Jupyter executes top-to-bottom and tolerates stale state, which is why roughly 96% of public Jupyter notebooks fail to reproduce.
  • Marimo notebooks are stored as pure Python files, so they diff cleanly in Git and run as standalone scripts (no .ipynb JSON noise).
  • Every Marimo notebook ships as a deployable web app via marimo run or a WASM-compiled static page. Jupyter needs Voila or Streamlit for the same thing.
  • The 2026 marimo pair release embeds AI agents directly inside a notebook session, with secure token auth and live cell execution.
  • Jupyter still wins on multi-language kernels (R, Julia, SQL kernels), ecosystem maturity, and quick throwaway exploration where reactivity is overkill.
  • Patch to Marimo 0.23.0 or later to fix CVE-2026-39987, a WebSocket authentication bypass in the terminal endpoint.

What is Marimo and how does it differ from Jupyter?

Marimo is an open-source Python notebook that replaces Jupyter's linear, manually executed cell model with a reactive dataflow graph. When you run a cell or change a UI value, Marimo inspects the variables each cell defines and reads, builds a directed acyclic graph (DAG), and automatically re-runs every downstream cell. The mental model is basically a spreadsheet. So if you've ever opened an old Jupyter notebook, hit "Run All", and watched it produce different numbers than the cached outputs, you already understand the problem Marimo solves.

The project, launched by Akshay Agrawal and now maintained by the marimo-team organisation, has grown fast. By May 2026 it counts over 20,000 GitHub stars, 230+ contributors, and a 3,600-member Discord. Marimo's tagline ("a next-generation Python notebook") covers four design pillars: reactivity, reproducibility, a pure-Python file format, and first-class deployment.

Jupyter remains the reigning standard. JupyterLab 4.x and Jupyter Notebook 7.x share the same frontend codebase, support 40+ language kernels, and integrate with virtually every cloud data platform. But Jupyter's strengths (freeform exploration, polyglot kernels, ecosystem) come with structural weaknesses Marimo was explicitly built to fix. The two tools target overlapping but distinct audiences, and the right choice depends on what you actually do day to day.

Marimo vs Jupyter feature comparison

The table below summarises the differences engineers cite most often when choosing between the two notebooks. I've verified each row against the official Marimo documentation and Jupyter's project pages for the 2026 releases (Marimo 0.23.x, JupyterLab 4.4.x).

DimensionMarimo (0.23.x, 2026)Jupyter (Lab 4.4 / Notebook 7)
Execution modelReactive DAG; dependent cells auto-runLinear, manual; out-of-order execution allowed
File formatPure Python .py (PEP 8 compliant)JSON .ipynb with embedded base64 outputs
Git diffsClean, line-levelNoisy; needs nbdime or jupytext
Hidden stateImpossible by designCommon (most notebooks don't reproduce)
UI widgetsBuilt-in mo.ui, reactive by defaultipywidgets with manual callbacks
SQL cellsNative mo.sql on DuckDB, Polars, PandasRequires jupysql or ipython-sql
Deploymentmarimo run, WASM static site, DockerVoila, Streamlit, or Panel required
Language kernelsPython only40+ kernels (R, Julia, Scala, SQL, …)
AI integrationmarimo pair with Claude Code, Codex, OpenCodeJupyterAI extension, Copilot via VS Code
LicenseApache 2.0, free foreverBSD 3-Clause, free forever

Two takeaways from the table. Marimo wins decisively on reproducibility, version control, and deployment, while Jupyter retains the ecosystem advantage. If you live entirely in Python and ship code that other people need to re-run six months later, Marimo's tradeoffs are compelling. If you teach a polyglot data course or rely on R kernels, Jupyter is still the safer base.

How does Marimo's reactive execution actually work?

Marimo parses each cell's abstract syntax tree (AST) at edit time. It records two sets per cell: the variables the cell defines (left-hand-side assignments, function and class definitions) and the variables it reads. These sets form a dependency graph: cell B depends on cell A if B reads any variable A defines. When you execute A or modify it, Marimo invalidates A and every transitive descendant, then re-runs them in topological order.

Two constraints fall out of this design. First, a variable can be defined in only one cell, otherwise the graph would have ambiguous edges. Second, when you delete or rename a definition, its dependents must update too, which Marimo does automatically. The result is a notebook whose visible state always matches the source code on disk, with no phantom variables left over from a deleted cell.

Lazy mode for expensive cells

Reactive re-execution is great for fast cells but ruinous if every change retrains a 30-minute model. (Ask me how I know.) Marimo offers lazy mode: stale cells are flagged in yellow and only re-run when you click them. You can also wrap expensive computations in mo.persistent_cache(), which keys results by input hashes and survives kernel restarts. Same spirit as functools.lru_cache, just persisted to disk.

import marimo as mo

with mo.persistent_cache("training"):
    model = expensive_train(X_train, y_train)

# Subsequent runs with the same X_train, y_train return cached `model`
# in milliseconds instead of minutes.

File format, Git diffs, and version control

A Jupyter .ipynb file is a JSON document that interleaves source code, metadata, and base64-encoded outputs (images, HTML widgets, plot data). Even a one-line change in a cell shows up as a noisy hunk in git diff, and binary cell outputs balloon the file beyond what most code-review tools render. Teams typically pair Jupyter with jupytext (to sync to Markdown or scripts) or nbstripout (to strip outputs before commit), which adds friction.

Marimo sidesteps the problem by storing notebooks as plain Python modules. A cell is just a function annotated with @app.cell:

import marimo

app = marimo.App()

@app.cell
def __():
    import pandas as pd
    import marimo as mo
    return mo, pd

@app.cell
def __(pd):
    df = pd.read_csv("sales.csv")
    df.head()
    return df,

if __name__ == "__main__":
    app.run()

Because the file is valid Python, you can lint it with Ruff, type-check it with mypy, import it from other modules, and execute it as a script with python notebook.py. Git diffs show only the lines you actually changed. Pull-request review becomes a normal code review, no special tooling required. For teams that already enforce code-quality gates, this is honestly the single biggest reason to migrate.

Install Marimo and build your first reactive notebook

Marimo requires Python 3.10 or newer. Install it into a project virtual environment:

python -m venv .venv
source .venv/bin/activate
pip install --upgrade "marimo>=0.23.0" pandas matplotlib

marimo new sales_dashboard.py

The marimo new command opens a browser-based editor at http://localhost:2718 and creates a starter file. Type a few cells:

import marimo as mo
import pandas as pd

# Cell 2 -- file loader
df = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv")

# Cell 3 -- interactive day selector
day = mo.ui.dropdown(options=df["day"].unique().tolist(), value="Sun", label="Day")
day

# Cell 4 -- reactive aggregation (depends on `df` and `day`)
filtered = df[df["day"] == day.value]
filtered.groupby("sex")["tip"].mean().to_frame("avg_tip")

Click a different day in the dropdown and Marimo automatically re-executes cell 4. No callbacks, no display() calls, no observer registration. The UI element is just a variable, and the reactive graph does the rest. Compare this to the equivalent Jupyter pattern, which requires ipywidgets.interact or manual observe() handlers and breaks the moment you reorder cells.

Interactive widgets, dataframes, and SQL cells

Marimo's mo.ui module ships 30+ widget types: sliders, dropdowns, file uploaders, date pickers, code editors, multi-selects, refresh buttons, and form composites. Each widget is bound to a Python variable so downstream cells re-execute on change. The 2026 releases added editable matrix inputs and reactive Matplotlib selections, letting you brush a region of a chart and feed the selected indices back into Python without writing JavaScript.

The unified dataframe viewer

Output any pandas, Polars, or PyArrow DataFrame and Marimo renders an interactive table with sort buttons, a column-options menu, type-aware filters, and summary statistics (count, sum, mean) that appear when you select cells, spreadsheet-style. The viewer streams data lazily, so 10-million-row Polars frames stay responsive. For broader Polars and DuckDB workflows, see our deep dive on Polars and DuckDB for Python data science.

Native SQL cells

Marimo's mo.sql() compiles to DuckDB by default and can query Pandas/Polars DataFrames in-place. Cells written as SQL participate in the reactive graph just like Python cells:

result = mo.sql(f"""
    SELECT day, AVG(tip) AS avg_tip
    FROM df
    WHERE total_bill > {min_bill.value}
    GROUP BY day
    ORDER BY avg_tip DESC
""")

Change the min_bill slider and the SQL re-executes. The same pattern in Jupyter requires jupysql plus magic commands and offers no automatic dependency tracking. If your workflow is heavily chart-driven, pair Marimo with the libraries covered in our 2026 Python data visualization guide. Reactive Plotly figures landed in Marimo 0.19 and now cover most chart types.

Deployment: marimo run, WASM, and Docker

Turning a notebook into a shareable app is where Marimo's design pays off the most. Three deployment paths cover almost every scenario:

  1. App server. marimo run sales_dashboard.py --port 8000 launches a production-mode server that hides code by default, shows only outputs and UI, and supports multi-user sessions. Stick it behind nginx or a Cloud Run container for internal tools.
  2. WASM static site. marimo export html-wasm sales_dashboard.py -o site/ compiles the notebook (and its Python dependencies via Pyodide) into a static HTML/JS bundle that runs entirely in the browser. Drop the output onto GitHub Pages, Netlify, or any static host. No server, no Python runtime, no cold starts.
  3. PDF / slide export. marimo export pdf --as=slides --rasterize-outputs generates presentation-ready PDFs from notebooks laid out in slide view, with widget snapshots captured as images.

Jupyter has no equivalent first-party deployment story. You can wrap a notebook with Voila, but Voila ships only the kernel-backed widget runtime; you still need a Python server. Streamlit and Gradio rewrite the same logic in their own DSLs. Marimo's "the notebook is the app" approach removes the porting step entirely. An experiment-tracking dashboard you built on MLflow 3 logs can be deployed without changing a line of code.

AI workflows with marimo pair

The headline 2026 feature is marimo pair, an agent skill that drops AI coding agents into a live notebook session. Launch it with:

marimo edit dashboard.py
# Then in the notebook menu, click "Pair with an agent"
# Or from the CLI:
marimo pair --with-token $MARIMO_PAIR_TOKEN dashboard.py

The token-based handshake keeps credentials out of shell history. Once paired, the agent (Claude Code, Codex, or OpenCode) can inspect live variables, execute cells, install packages with uv pip install, and propose edits that are applied through the reactive graph. Because the file is plain Python, the agent reads it natively without a custom .ipynb parser. This is exactly the workflow that Jupyter struggles with: agents working on Jupyter must either round-trip through jupytext or parse JSON with embedded outputs.

I hit this firsthand on a recent ML side project. The first time I asked Claude Code to "add Optuna and re-run the comparison plot" in a Marimo session, the edit landed in the search-space cell and reactivity took over: the trial loop reran, the leaderboard table refreshed, and the chart updated, all without a single manual cell click. For deeper tuning patterns, see our Optuna and Bayesian optimization guide. The full marimo release notes on GitHub document the pair API and the recent OpenTelemetry tracing support that lets you observe agent-driven sessions in production.

Migrating Jupyter notebooks to Marimo

Marimo ships a one-shot converter that ingests .ipynb files and rewrites them as reactive Python modules:

marimo convert analysis.ipynb -o analysis.py
marimo edit analysis.py

The converter handles the easy cases automatically: imports, function and class definitions, plain expressions, and Markdown cells (which become mo.md(...) calls). It will flag two patterns that need manual cleanup:

  • Multiple-definition cells. If your notebook redefines df = ... in five places, Marimo will refuse to load it. Refactor to a single definition or use intermediate names (df_clean, df_filtered).
  • Side-effecting cells. Cells that mutate global state (modifying df in place rather than returning a new frame) often work in Jupyter but become non-deterministic under reactivity. Convert mutations into pure expressions or wrap them in functions.

For teams running JupyterHub, the marimo-jupyter-extension (v0.2.2, April 2026) adds Marimo as a launcher tile inside JupyterLab. You can run both notebook types side by side during the transition and convert one at a time without disrupting users.

When Jupyter is still the right choice

Marimo isn't a strict superset of Jupyter, and several workflows are better served by the older tool:

  • Polyglot work. If you teach a course that mixes Python, R, and Julia in one document, Jupyter's kernel architecture is unmatched. Marimo is Python-only by design.
  • Quick throwaway exploration. Opening a CSV, running df.describe(), and closing the tab is faster in Jupyter, where you don't need to think about variable redefinitions. Marimo's strictness pays off over longer-lived notebooks.
  • Outputs as artefacts. Jupyter persists cell outputs inside the .ipynb, so a notebook can serve as a static report even without a Python runtime. Marimo intentionally drops outputs (they live only in memory); for static reports, you must export to HTML, PDF, or WASM.
  • Existing kernel-coupled tooling. Papermill, nbconvert, and Voila all assume the .ipynb contract. If your CI/CD pipeline parameterises notebooks at scale, the migration cost may exceed the benefit.

Most teams will find a hybrid is fine. Keep Jupyter for ad-hoc exploration and teaching, adopt Marimo for production data apps, dashboards, and shared experimental workflows where reproducibility matters. The two notebooks coexist happily in the same project.

Frequently Asked Questions

Is Marimo better than Jupyter?

Marimo is better for reproducible, version-controlled Python workflows and for shipping notebooks as web apps, thanks to its reactive execution model and pure-Python file format. Jupyter remains better for polyglot teaching, quick throwaway exploration, and ecosystems built around .ipynb tooling like Papermill or Voila.

Can Marimo open and run existing .ipynb files?

Not directly. Use marimo convert notebook.ipynb -o notebook.py to translate the file. The converter handles most cells automatically; you may need to fix cells that redefine the same variable in multiple places, which Marimo prohibits by design.

Is Marimo free and open source?

Yes. Marimo is licensed under Apache 2.0, free for personal and commercial use, and developed publicly at github.com/marimo-team/marimo. The hosted edition (marimo Cloud) is a separate paid service, but the core notebook is fully featured locally.

Does Marimo work with VS Code?

Yes. The official Marimo VS Code extension provides syntax highlighting, cell navigation, and a "Run notebook" command that opens the reactive editor in a side panel. Because Marimo files are plain Python, all standard VS Code features (Pylance, Copilot, GitLens) work without modification.

How does Marimo handle hidden state?

It eliminates it. When you delete or rewrite a cell, Marimo also deletes the variables that cell defined and re-runs every dependent cell. There is no way to have a variable in memory that doesn't correspond to code in the current notebook. The dataflow graph guarantees the in-memory state and the source file always agree.

What is CVE-2026-39987 and do I need to patch?

CVE-2026-39987 is a WebSocket authentication bypass in Marimo's embedded terminal endpoint that allowed unauthenticated remote code execution. Upgrade to Marimo 0.23.0 or later with pip install --upgrade "marimo>=0.23.0". Self-hosted deployments behind a public network should patch immediately.

Article changelog (1)
  • — Expanded with TL;DR, table of contents, or additional sections
About the Author Sofia Castellanos

Sofia is a Python data engineer with 7 years building ingestion and transformation systems for media and adtech. She spent three years at Spotify on the personalization-data team, where she shipped a streaming-to-batch reconciliation pipeline that processes around 90 billion playback events per day, and two years before that at The New York Times on the subscriber-analytics platform. She focuses her writing on production pandas patterns (chunked reads, categorical memory tricks, Arrow interop), Airflow 2.x task groups, and the kinds of dbt + Python hybrid pipelines that show up once your warehouse bill stops being cute. She also maintains pyspark-helpers, a small library for column-name munging she keeps porting between jobs. Sofia is based in Madrid, originally from Bogota, and a relentless defender of type hints in notebook code.