marimo: The Python Notebook That Refuses to Lie

A reactive, pure-Python notebook that replaces hidden state with a dependency graph, so exploratory code behaves more like software and less like a trap.

9 min read View on GitHub More from marimo-team

A tangled notebook page on the left is reorganized into a clean dependency graph on the right. The scene explains how marimo turns loose, order-dependent cells into a system where changes propagate only through explicit data dependencies.
marimo does not just add a nicer interface. It changes the execution contract from linear notebook drift to tracked dependencies.
Key Takeaways

Why notebooks break trust

Traditional notebooks have a simple promise and a nasty habit. They let you explore quickly, then quietly let execution drift away from the source code that produced the result. Run cells out of order, mutate a variable in the wrong place, and the notebook starts lying with a straight face.

That is the problem marimo is built to remove. Instead of treating cells as a loose sequence, it treats them as a program with dependencies, so a change in one place propagates only where it should.

marimo is a reinvention of the Python notebook as a reproducible, interactive, and shareable Python program, instead of an error-prone JSON scratchpad.

Akshay Agrawal, Co-Founder, CEO · The marimo notebook - Team

marimo’s answer: a notebook that behaves like a graph

marimo’s core idea is simple to say and hard to get right: cells are nodes, variable use creates edges, and edits trigger targeted recomputation. Change a value at the top of the graph, and marimo reruns the dependent cells downstream instead of blasting the whole notebook back to square one.

The point of marimo is selective recomputation. Only the cells that depend on a change need to move.

That is a deeper shift than a new UI convention. The notebook stops being a record of what you happened to run and becomes a graph the runtime can reason about. Once that happens, the notebook can enforce consistency instead of merely displaying it.

Pure Python is not a file-format quirk

marimo stores notebooks as plain `.py` files. That sounds like a convenience feature until you compare it with the messier reality of `.ipynb`, where the notebook is also a JSON document, a UI artifact, and a source of merge pain.

Dimensionmarimo (.py)Jupyter (.ipynb)
Version controlReadable diffs and ordinary mergesJSON noise and frequent conflicts
Execution modelReactive dependency graphManual, linear cell execution
ToolingWorks with Python tooling directlyNeeds notebook-aware tooling
ReusabilityRunnable as a scriptNotebook-first, script-second
AI friendlinessEasy for code models to inspectHarder to reason about as structured code
Best fitExploration that should stay reproducibleFast ad hoc experimentation

This matters because the file format is part of the product. A `.py` notebook can be linted, tested, imported, and reviewed like code, which means the notebook no longer sits outside the normal software workflow. It joins it.

import marimo as mo

app = mo.App()

@app.cell
def _(x):
    y = x + 1
    return y

@app.cell
def _(y):
    return y * 2

How the runtime keeps state honest

Under the hood, marimo uses static analysis to map which cells depend on which names, then keeps the notebook aligned with that map. When a value changes, only the downstream cells rerun. When a cell is deleted, marimo scrubs its variables from memory so zombie state does not linger.

A close-up of a notebook cell drawn like a control chamber with valves, switches, and branching pipes. One variable change sends light through a dependent chain while an unrelated branch stays dark, and a heavy cell is held behind a gate to show lazy execution.
Selective recomputation is the whole trick. marimo reacts where it must and holds back where it should.

That last point is easy to miss and worth underlining. A notebook with stale values is worse than no notebook at all because it looks reliable while preserving contradictions. marimo is trying to remove those contradictions at the runtime level.

What the rest of the stack is doing

The architecture is polyglot for a reason. The Python backend owns the notebook model, the TypeScript frontend owns the editor experience, and code generation bridges the two so the contract stays tight. The result is a product that feels interactive without becoming loose.

Python backend -> OpenAPI schema -> TypeScript frontend
                   ^                     |
                   |                     v
               notebook runtime     editor UI and widgets

That stack also explains why marimo can move between notebook, app, and browser-native execution modes without changing its core model. The surface area is broad, but the execution rule stays the same.

Where marimo sits among notebook and app tools

marimo is easiest to understand by contrast. Jupyter gives you maximum freedom and maximum chances to fool yourself. Streamlit reruns the whole script on interaction. Pluto offers reactive notebook semantics, but in Julia. Shiny gives you a powerful reactive system, though with a more explicit mental model than many Python users want.

ToolExecution modelFile formatHidden state riskBest use case
marimoReactive DAG with selective rerunsPure .pyLowInteractive analysis that should stay reproducible
JupyterManual linear execution.ipynbHighLoose exploration and teaching
StreamlitFull script rerun on changePython scriptMediumData apps with simple control flow
PlutoReactive notebook graphJulia notebooksLowNotebook-native reactive computing
ShinyReactive inputs and outputsApp codeLowProduction-grade reactive apps

marimo's reactivity, built-in affordances for working with data (table viewer, database connections, and other interactive elements), lazy execution, and persistent caching make me far more productive when working with data, regardless of whether I am making an app-like thing.

Akshay Agrawal, Co-Founder, CEO · Hacker News Discussion

That is the cleanest way to think about the project. marimo is not trying to out-Jupyter Jupyter or out-Streamlit Streamlit. It is trying to make notebooks honest enough to be trusted as programs, while still feeling light enough for exploration.

Why this architecture matters

The larger ambition is bigger than notebooks. marimo is trying to erase the boundary between exploratory work, reproducible code, and shareable interactive artifacts. If it succeeds, the notebook stops being a fragile scratchpad and starts becoming a disciplined Python surface that can survive contact with real engineering practices.

That is the real point of the graph, the pure Python file, and the selective runtime. Together they make a notebook that behaves less like a trap and more like software.