Marimo vs Jupyter: Are Reactive Notebooks the Future?

Jupyter stores your analysis as a linear script of cells that you can run in any order, and that freedom is the source of its most notorious bug: hidden state. Marimo removes it. It treats a notebook as a dependency graph, so changing one cell automatically updates every cell that depends on it. The result is a notebook that behaves like a spreadsheet, stores itself as plain Python, and deploys as an app. Jupyter still wins on ecosystem, language support, and managed infrastructure.

Quick Takeaways

  • Execution model: Jupyter runs cells manually in any order. Marimo builds a directed acyclic graph (DAG) from variable definitions and references, then re-runs affected cells automatically.
  • File format: Jupyter saves .ipynb JSON with embedded outputs. Marimo saves pure .py files that diff cleanly in Git and run as scripts.
  • Ecosystem: Jupyter supports dozens of kernels, JupyterHub, Colab, and a huge extension library. Marimo is Python-first and younger.
  • Verdict: Use Marimo for reproducible analysis, interactive tools, and apps. Keep Jupyter for multi-language work, managed platforms, and teams locked into the existing ecosystem.
Dimension Jupyter Marimo
Execution model Imperative, manual, any order Reactive DAG, automatic
Hidden state Common Eliminated by design
File format JSON (.ipynb) Python (.py)
Git diffs Noisy Clean
Interactive UI ipywidgets (callback-based) mo.ui elements (reactive)
App deployment Voilà, Streamlit (separate tools) Built in (marimo run)
Languages Python, R, Julia, and more Python (with SQL and Markdown)
Maturity Very high Growing

Why Hidden State Breaks Notebook Reproducibility

Jupyter’s kernel is a long-lived Python process. Each cell mutates that process’s memory, and the notebook file records none of the execution order. Consider this sequence:

  1. You run cell 1: threshold = 0.5.
  2. You run cell 2: df_filtered = df[df["score"] > threshold].
  3. You scroll back up, change cell 1 to threshold = 0.8, and run only that cell.
  4. Cell 2 still displays results from 0.5.

Your screen now shows outputs that don’t match your code. Delete a cell and its variable keeps living in memory, so downstream cells keep working until someone restarts the kernel and the notebook crashes.

This is measurable. A 2019 large-scale study of public GitHub notebooks (Pimentel et al.) found that only a minority executed without errors, and a far smaller fraction reproduced the original outputs. Out-of-order execution and missing dependencies drove most failures.

Marimo attacks this at the root. It parses each cell with static analysis, records which variables it defines and which it references, and builds a graph. When you change a cell, marimo re-runs its descendants. If you delete a cell, marimo removes its variables from memory and invalidates dependents.

How Marimo’s Reactive Execution Works

The DAG Rules

Marimo enforces three constraints that make the graph sound:

  • One definition per variable. Two cells cannot define the same global name.
  • No cycles. Cell A cannot depend on cell B if B depends on A.
  • Static references. Marimo infers dependencies from code, not from runtime behavior.

These rules feel restrictive for about a day. They also guarantee that notebook state is a pure function of the code on screen.

A Minimal Reactive Notebook

Install and launch the editor:

pip install marimo          # install the package
marimo edit analysis.py     # create/open a notebook in the browser

Here is the underlying file. It is ordinary Python:

import marimo

app = marimo.App()


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


@app.cell
def _(mo):
    # A UI element: its .value is tracked by the reactive graph
    n_samples = mo.ui.slider(start=100, stop=5000, step=100, value=1000, label="Samples")
    n_samples
    return (n_samples,)


@app.cell
def _(n_samples, np, pd):
    # Re-runs automatically whenever the slider moves
    rng = np.random.default_rng(42)
    df = pd.DataFrame({"x": rng.normal(size=n_samples.value)})
    df.describe()
    return (df,)


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

Drag the slider and the DataFrame summary updates with no callback, no observe() handler, and no manual re-run. Because the file ends with app.run(), you can also execute it with python analysis.py.

Controlling Expensive Cells

Auto-execution is risky when a cell trains a model for ten minutes. Marimo provides three controls:

  • Lazy runtime mode: marks dependent cells as stale instead of re-running them.
  • mo.stop(condition): halts a cell until a condition, such as a button click, is met.
  • @mo.cache: memoizes function results based on inputs.
@app.cell
def _(mo):
    run_button = mo.ui.run_button(label="Train model")
    run_button
    return (run_button,)


@app.cell
def _(mo, run_button):
    # Block expensive work until the user clicks the button
    mo.stop(not run_button.value, mo.md("Click **Train model** to start."))
    # ... model training code here ...
    return

Jupyter vs Marimo: Detailed Technical Comparison

Version Control and Code Review

An .ipynb file is JSON that bundles source, outputs, execution counts, and metadata. A one-line code change can produce hundreds of changed lines, including base64-encoded images. Teams patch this with tools like nbstripout, Jupytext, or nbdime.

Marimo notebooks are Python files. Diffs show only code changes, and standard linters, formatters, and test runners work without adapters. This single difference often decides adoption for teams that review notebooks in pull requests.

Dependency and Environment Management

Task Jupyter Marimo
Declare dependencies External (requirements.txt, conda env) Optional inline script metadata (PEP 723)
Isolated run Manual venv + kernel registration marimo edit –sandbox with uv
Share a runnable notebook Ship env file + notebook Single .py file with embedded dependencies

Marimo’s sandbox mode installs packages declared in the file into a temporary environment. That makes a notebook closer to a self-contained artifact.

Interactivity and Apps

Jupyter relies on ipywidgets, which use callbacks to push state changes. Turning a notebook into a shareable app typically means adopting Voilà, Streamlit, or Panel, then restructuring code.

Marimo uses one model for exploration and delivery:

marimo edit analysis.py   # develop with code visible
marimo run analysis.py    # serve as a read-only app, code hidden

Marimo also exports to static HTML (marimo export html) and can run in the browser through WebAssembly, which removes the need for a server for lightweight apps.

Language and Tooling Support

Jupyter’s kernel protocol supports Python, R, Julia, Scala, and many others. Its ecosystem includes JupyterHub for multi-user deployments, nbconvert for export, Papermill for parameterized execution, and integrations in VS Code, Google Colab, Databricks, and SageMaker.

Marimo is Python-centric. It offers SQL cells (querying DataFrames and databases such as DuckDB) and Markdown, but it does not replace an R or Julia workflow.

Performance and Scalability

Marimo’s reactive engine adds graph analysis overhead, which is negligible for typical notebooks. The real scaling concern is the same in both tools: a large DataFrame lives in one process’s memory. For big data, push computation to PySpark, DuckDB, Polars, or a warehouse, and display only aggregated results.

Migrating from Jupyter to Marimo

Marimo ships a converter:

# Convert an existing notebook; review the output for duplicate variable names
marimo convert legacy_analysis.ipynb -o legacy_analysis.py
marimo edit legacy_analysis.py

Expect to fix three things:

  1. Redefined variables. Notebooks often reuse names like df or temp across cells. Rename them or wrap logic in functions.
  2. Mutation across cells. Code like df["new"] = ... in a different cell than where df was defined breaks the one-definition rule. Create a new variable (df_features = df.assign(...)) instead.
  3. Magic commands. Replace %matplotlib inline and similar with regular imports or marimo equivalents.

These fixes tend to improve code quality regardless of the tool.

Real-World Use Cases

Predicting Customer Churn with Interactive Thresholds

A churn model outputs probabilities. The business question is where to set the decision threshold. In Jupyter, an analyst reruns cells repeatedly or builds an ipywidgets callback. In Marimo, a slider feeds the threshold into the metrics cell:

@app.cell
def _(mo):
    threshold = mo.ui.slider(0.05, 0.95, step=0.05, value=0.5, label="Churn threshold")
    threshold
    return (threshold,)


@app.cell
def _(threshold, y_true, y_proba):
    from sklearn.metrics import precision_score, recall_score, f1_score

    y_pred = (y_proba >= threshold.value).astype(int)  # recompute on every slider move
    {
        "precision": round(precision_score(y_true, y_pred), 3),
        "recall": round(recall_score(y_true, y_pred), 3),
        "f1": round(f1_score(y_true, y_pred), 3),
    }
    return

Stakeholders drag the slider and watch precision and recall trade off in real time, then receive the same file as an app via marimo run.

Handling Missing Data in Sensor Streams

Engineers comparing imputation strategies (forward fill, linear interpolation, rolling median) can bind a mo.ui.dropdown to the strategy and plot the repaired series. Because the DAG guarantees consistent state, teammates reproduce the same figure from the same file, with no kernel-restart ritual.

Where Jupyter Remains the Better Choice

  • Polyglot teams using R or Julia alongside Python.
  • Managed platforms (Databricks, SageMaker, Colab, JupyterHub) built around .ipynb.
  • Teaching materials with thousands of existing notebooks.
  • Long, stateful exploration where you want to hold a loaded model in memory and probe it manually without triggering re-execution.

Are Reactive Notebooks the Future?

Reactive execution solves a real, documented problem, and it has precedent: spreadsheets, Observable, and Pluto.jl all use dependency-driven evaluation. Marimo brings that model to Python with a file format that fits modern software practice.

Adoption depends on factors beyond technical merit. Jupyter’s network effects are enormous: cloud vendors, universities, and thousands of tutorials assume .ipynb. A realistic outlook is coexistence. Jupyter stays the default for exploration inside established platforms, while reactive notebooks gain ground where reproducibility, code review, and app delivery matter.

A practical test: if you have ever sent a colleague a notebook that “worked on my machine,” run a small project in Marimo this week and compare the experience.

Decision Framework

If your priority is… Choose
Reproducibility and no hidden state Marimo
Clean Git history and PR reviews Marimo
Sharing results as interactive apps Marimo
R, Julia, or multi-language kernels Jupyter
Enterprise platform integration Jupyter
Maximum extension and community support Jupyter

FAQ

Is Marimo better than Jupyter?

Marimo is better for reproducibility, version control, and interactive apps because it uses a reactive DAG and plain .py files. Jupyter is better for multi-language support and platform integration. The right choice depends on your workflow.

Can Marimo open Jupyter notebooks?

Not directly, but it converts them. Run marimo convert notebook.ipynb -o notebook.py, then fix any redefined variables, since Marimo allows only one definition per name.

Does Marimo replace Jupyter completely?

No. Marimo supports Python (plus SQL and Markdown) and lacks the kernel ecosystem and managed-platform integrations that Jupyter has. Many teams use both.

How does Marimo prevent hidden state?

It analyzes each cell’s variable definitions and references to build a dependency graph. Changing or deleting a cell updates or invalidates every dependent cell, so displayed outputs always match the code.

Hot this week

Revive an Old PC With Linux After Windows 10: The Best Options

Windows 10 support ended. Learn which Linux distro fits your old PC, how to install it, and how to fix common boot issues. Start reviving your hardware today.

Linux Is Now Wayland-First: What It Means for Your Desktop (X11 vs Wayland Explained)

Your Linux desktop probably already runs Wayland, and the option to go back is disappearing. GNOME 50 removed the X11 session entirely, so Wayland is the only display server available at login.

Ubuntu 26.04 LTS: What’s New and Should You Upgrade?

Ubuntu 26.04 LTS brings Linux 7.0, GNOME 50, and Wayland-only. See what changed, what breaks, and how to upgrade from 24.04 safely. Read the guide.

Intel Macs and macOS 27: What the End of Support and Rosetta Means

macOS 27 Golden Gate is Apple silicon only, and Rosetta 2 ends in macOS 28. Audit your Intel apps, plan your Mac, and fix breakage. Read the guide.

macOS 27: What’s New, Compatibility, and Should You Upgrade?

macOS 27 Golden Gate drops Intel, adds Siri AI, and ships with early bugs. Check compatibility, fix known issues, and decide when to upgrade. Read the guide.

Topics

Revive an Old PC With Linux After Windows 10: The Best Options

Windows 10 support ended. Learn which Linux distro fits your old PC, how to install it, and how to fix common boot issues. Start reviving your hardware today.

Linux Is Now Wayland-First: What It Means for Your Desktop (X11 vs Wayland Explained)

Your Linux desktop probably already runs Wayland, and the option to go back is disappearing. GNOME 50 removed the X11 session entirely, so Wayland is the only display server available at login.

Ubuntu 26.04 LTS: What’s New and Should You Upgrade?

Ubuntu 26.04 LTS brings Linux 7.0, GNOME 50, and Wayland-only. See what changed, what breaks, and how to upgrade from 24.04 safely. Read the guide.

Intel Macs and macOS 27: What the End of Support and Rosetta Means

macOS 27 Golden Gate is Apple silicon only, and Rosetta 2 ends in macOS 28. Audit your Intel apps, plan your Mac, and fix breakage. Read the guide.

macOS 27: What’s New, Compatibility, and Should You Upgrade?

macOS 27 Golden Gate drops Intel, adds Siri AI, and ships with early bugs. Check compatibility, fix known issues, and decide when to upgrade. Read the guide.

iOS 27: What’s New and Should You Update?

iOS 27 is live. See every new feature, compatible iPhones, known bugs, and a clear verdict on whether to update now or wait. Read the guide.

Android 17: What’s New and Which Phones Get It

Android 17 is live: App Bubbles, location indicators, app memory limits. See which Pixel, Samsung, OnePlus and Xiaomi phones get it. Check yours now.

Android Developer Verification Explained: What Changes for Sideloading

Android developer verification is live. See how the 24-hour advanced flow works, what ADB skips, and how to keep sideloading safely. Read the guide.

Related Articles

Popular Categories