Control Flow API Reference
Control cell execution flow with conditional stops, gating, and threading.
Conditional Execution
mo.stop
Halt cell execution based on a condition.
import marimo as mo
# Stop if condition is true
mo.stop(data is None)
# Stop with message
mo.stop(
not data_loaded,
mo.md("**Loading data...** Please wait.")
)
# Stop with UI element
mo.stop(
form.value is None,
mo.md("**Submit the form to continue.**")
)
When mo.stop() triggers:
- A
MarimoStopErroris raised - Cell execution halts
- The optional output is displayed
- Downstream cells don't run (their definitions are removed)
Common Patterns
Gate on button click:
run_button = mo.ui.run_button(label="Run Analysis")
# In another cell
mo.stop(not run_button.value, mo.md("Click **Run Analysis** to proceed."))
# Expensive computation runs only after button click
result = expensive_analysis(data)
Gate on form submission:
form = mo.ui.text(label="Enter query").form()
# In another cell
mo.stop(form.value is None, mo.md("Submit a query to search."))
results = search(form.value)
Gate on data availability:
mo.stop(
df is None or len(df) == 0,
mo.callout("No data available. Upload a file to continue.", kind="warn")
)
# Process data
summary = df.describe()
Multiple conditions:
errors = []
if not username:
errors.append("Username required")
if not password:
errors.append("Password required")
mo.stop(
len(errors) > 0,
mo.callout(mo.md("\n".join(f"- {e}" for e in errors)), kind="danger")
)
Periodic Execution
mo.ui.refresh
Trigger cell re-execution at intervals.
# Auto-refresh every 5 seconds
refresh = mo.ui.refresh(interval=5)
# With options
refresh = mo.ui.refresh(
options=["1s", "5s", "10s", "30s", "off"],
default_interval="5s"
)
# In another cell - reference refresh to trigger updates
_ = refresh.value
current_time = datetime.now()
latest_data = fetch_data()
Runtime Configuration
Lazy Execution Mode
Configure via notebook settings to mark cells as stale instead of auto-running.
In the editor:
- Open settings (gear icon)
- Enable "Lazy execution"
- Cells show "stale" indicator when dependencies change
- Click to run manually
Useful for:
- Expensive computations
- Debugging
- Step-by-step execution
Disable Autorun
In notebook settings:
- Disable "Autorun on startup"
- Disable "Autorun on cell execution"
Threading
mo.Thread
Extended threading with frontend communication.
import marimo as mo
def worker():
thread = mo.current_thread()
for i in range(100):
if thread.should_exit:
# Cleanup and exit
return
# Do work
mo.output.append(f"Progress: {i}%")
thread = mo.Thread(target=worker)
thread.start()
Properties:
should_exit: ReturnsTruewhen cell becomes invalid (re-run, deleted, interrupted)
Features:
mo.output.append()andprint()forward to frontend- Must implement cleanup when
should_exitisTrue
mo.current_thread
Get the current marimo thread context.
def worker():
thread = mo.current_thread()
while not thread.should_exit:
# Process items
pass
# Raises RuntimeError if not in a marimo thread
Error Handling
try:
result = risky_operation()
except Exception as e:
mo.stop(True, mo.callout(f"Error: {e}", kind="danger"))
# Continue with result
process(result)
Combining Control Flow
# Complex gating example
file = mo.ui.file(filetypes=[".csv"])
run_btn = mo.ui.run_button(label="Analyze")
mo.vstack([file, run_btn])
# In processing cell
mo.stop(file.value is None, mo.md("Upload a CSV file"))
mo.stop(not run_btn.value, mo.md("Click **Analyze** to process"))
# Both conditions met - process
df = pd.read_csv(io.BytesIO(file.value[0].contents))
analysis = analyze(df)
Best Practices
- Use mo.stop() for prerequisites: Gate expensive operations until dependencies are ready
- Provide clear feedback: Always include informative output in
mo.stop() - Use run_button for expensive ops: Don't auto-run costly computations
- Handle thread cleanup: Always check
should_exitin long-running threads - Prefer reactivity over manual control: Let marimo's DAG handle most execution flow