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.
- marimo’s real invention is not a prettier notebook UI, but a dependency-tracked execution model that makes hidden state much harder to smuggle in.
- Storing notebooks as pure .py files turns them into ordinary Python programs, which makes version control, linting, scripting, and AI tooling far more natural.
- The runtime reruns only the cells that depend on a change, which preserves interactivity without forcing a full notebook restart on every edit.
- marimo sits between notebooks and app frameworks because it keeps exploratory work interactive while enforcing enough structure to make results reproducible.
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.
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.
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.
| Dimension | marimo (.py) | Jupyter (.ipynb) |
|---|---|---|
| Version control | Readable diffs and ordinary merges | JSON noise and frequent conflicts |
| Execution model | Reactive dependency graph | Manual, linear cell execution |
| Tooling | Works with Python tooling directly | Needs notebook-aware tooling |
| Reusability | Runnable as a script | Notebook-first, script-second |
| AI friendliness | Easy for code models to inspect | Harder to reason about as structured code |
| Best fit | Exploration that should stay reproducible | Fast 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.
- Reactive means the runtime knows which cells depend on a change before it reruns them.
- Lazy mode lets expensive cells stay deferred instead of turning every edit into a full recomputation.
- Scrubbing deleted variables prevents old values from hanging around after the source cell is gone.
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.
| Tool | Execution model | File format | Hidden state risk | Best use case |
|---|---|---|---|---|
| marimo | Reactive DAG with selective reruns | Pure .py | Low | Interactive analysis that should stay reproducible |
| Jupyter | Manual linear execution | .ipynb | High | Loose exploration and teaching |
| Streamlit | Full script rerun on change | Python script | Medium | Data apps with simple control flow |
| Pluto | Reactive notebook graph | Julia notebooks | Low | Notebook-native reactive computing |
| Shiny | Reactive inputs and outputs | App code | Low | Production-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.
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.