Skip to content

Best practices

Here are best practices for writing marimo notebooks.

Use global variables sparingly. Keep the number of global variables in your program small to avoid name collisions. If you have intermediate variables, encapsulate them in functions or prefix them with an underscore (_tmp = ...) to make them local to a cell.

Use descriptive names. Use descriptive variable names, especially for global variables. This will help you minimize name clashes, and will also result in better code.

Use functions. Encapsulate logic into functions to avoid polluting the global namespace with temporary or intermediate variables, and to avoid code duplication.

Use mo.stop to stop execution. Use mo.stop to stop a cell from running when a condition is true; this is helpful when working with expensive notebooks. For example, prevent a cell from running until a button is clicked using mo.ui.run_button and mo.stop.

Expensive notebooks

For more tips on working with expensive notebooks, see the associated guide.

Use Python modules. If your notebook gets too long, split complex logic into helper Python modules and import them into your notebook. Use marimo's built-in module reloading to automatically bring changes from your modules into your notebook.

Minimize mutations. marimo does not track mutations to objects. Try to only mutate an object in the cell that creates it, or create new objects instead of mutating existing ones.

Example

Don't split up declarations and mutations over multiple cells. For example, don't do this:

l = [1, 2, 3]
l.append(new_item())

Instead, do declare and mutate in the same cell:

l = [1, 2, 3]
...
l.append(new_item())

or, if working in multiple cells, declare a new variable based on the old one:

l = [1, 2, 3]
extended_list = l + [new_item()]

Don't use state and on_change handlers. Don't use on_change handlers to react to UI interactions. Instead, use marimo's built-in reactive execution for interactive elements.

Write idempotent cells. Write cells whose outputs and behavior are the same when given the same inputs (references); such cells are called idempotent. This will help you avoid bugs and cache expensive intermediate computations.

.gitignore best practices

When using marimo with version control (e.g., git), you should ignore certain directories that marimo creates automatically. Add these patterns to your .gitignore file:

# Ignore all marimo auto-generated directories
**/__marimo__/

This single pattern covers all marimo-managed directories, including:

  • __marimo__/cache/ — persistent cache files created by mo.persistent_cache
  • __marimo__/session/ — session snapshot JSON files
  • __marimo__/assets/ — generated thumbnail images for OpenGraph previews

When to commit __marimo__ files

In most cases, you should exclude the entire __marimo__/ directory from version control. However, there are exceptions:

  • Session snapshots: If you want to include outputs in static HTML previews, commit the notebook's corresponding session JSON file in __marimo__/session/.
  • Thumbnails: If you've generated OpenGraph preview images, you may want to commit __marimo__/assets/<notebook_stem>/opengraph.png so they appear when sharing notebook links.

For these cases, you can use a more selective .gitignore:

# Ignore cache only (recommended for most projects)
**/__marimo__/cache/

# Allow session snapshots and assets to be committed when needed
!**/__marimo__/session/
!**/__marimo__/assets/