Marimo notebooks redefine the notebook experience by offering a reactive environment that addresses the limitations of traditional linear notebooks. With marimo, you can seamlessly reproduce and share content while benefiting from automatic cell updates and a correct execution order. Discover how marimo’s features make it an ideal tool for documenting research and learning activities.
By the end of this tutorial, you’ll understand that:
- Marimo notebooks automatically update dependent cells, ensuring consistent results across your work.
- Reactivity allows marimo to determine the correct running order of cells using a directed acyclic graph (DAG).
- Sandboxing in marimo creates isolated environments for notebooks, preventing package conflicts and ensuring reproducibility.
- You can add interactivity to marimo notebooks using UI elements like sliders and radio buttons.
- Traditional linear notebooks have inherent flaws, such as hidden state issues, that marimo addresses with its reactive design.
Before you can get started with marimo, you’ll need to install it. Fortunately, this is quick and easy to do:
$ python -m pip install marimo
You use pip
to install the marimo library on your system. With this done, it’s time to get started, be amazed, and learn all about a different type of notebook.
The best way to approach this tutorial is to use the instructions to complete the various examples and try the exercises yourself. If you want copies of the various notebook files created during the tutorial, you’ll find them in your download bundle. The README.md
file provides further details of what’s in your downloads.
Take the Quiz: Test your knowledge with our interactive “Marimo: A Reactive, Reproducible Notebook” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Marimo: A Reactive, Reproducible NotebookThis quiz is a great way to reinforce and build on what you've learned about marimo notebooks. You'll find most of the answers in the tutorial, but you'll need to do some additional research to find some of the answers.
How to Get Started in a Marimo Notebook
A notebook is a file where you can write your programming code, run it, and view the output. You can add formatted text to explain how your code works, display charts to clarify results, and even allow your notebook’s users to try out different scenarios using a customized user interface. Once your notebook is complete, you can save everything in a single file and share your creation with others.
In this section, you’ll learn to use marimo to create a simple notebook to perform a calculation and clearly display its results.
Like many notebooks, marimo notebooks consist of cells. The primary cell types are code cells, where you enter and run your Python code, and Markdown cells, where you enter formatted text to augment the code and its output.
In this first example, you’ll use a marimo notebook and NumPy to solve a pair of simultaneous equations. To do this, you’ll first need to install the NumPy library:
$ python -m pip install numpy
With NumPy installed, you can now create your notebook by typing the following command into your console:
$ marimo edit simultaneous_equations.py
When you run this command, you’ll most likely create a new notebook named simultaneous_equations.py
for editing. If you already have a marimo notebook with that name, you’ll open it instead. Either way, your notebook will be ready for you to use within your browser.
Switch to your web browser, and you’ll see your new notebook. It’ll contain a single cell. Hover your mouse over the cell to reveal a range of icons, each with a tooltip that explains its purpose and shows the associated keyboard shortcuts:
Each of the main icons are described in the screenshot above. While most of these are self-explanatory, there are some points you should be aware of:
- The red trash can icon shown here won’t appear immediately in your notebook. This is used to delete a cell and will only appear when you add other cells. You can’t see it yet because all notebooks must have at least one cell. Deleting the last cell is impossible.
- The color of the Run current cell icon is also significant. If this cell is white, as it is in the screenshot, it’s up to date and doesn’t need to be run. Once you start changing cells, you’ll see their Run icons develop a yellow tinge. This warns you that the cell has become stale, meaning you must run it to update it.
- Finally, notice that the numbers to the left of each cell indicate the line numbers of the code within the cell. Unlike most other notebooks, there are no numbers to indicate the running order of the cells. This is because marimo allows you to add code cells in any order. Marimo can work out the correct cell running order for itself. Even so, placing cells in an illogical order should be avoided.
When you hover your mouse over some of marimo’s icons, you’ll see their associated keyboard shortcuts. Unfortunately, they don’t work correctly in all browsers. If they don’t work for you, stick to using your mouse. Feel free to try them to find out if they work for you.
Adding Code and Markdown Content
It’s time for you to gain experience creating some content in marimo. By following the walk-through, you’ll get hands-on practice with the basics.
Although confusing the first time you see it, the single cell that contains import marimo as mo
is actually a blank cell. This code allows you to work with the marimo API. However, it’s not in the cell unless you type it in manually.
Click inside the cell in your notebook, which looks as though it contains import marimo as mo
, and type import marimo as mo
into the cell. Now, run the cell by clicking the cell’s Run icon, as shown in the screenshot above. Running this cell is necessary to allow you to work with Markdown in subsequent cells.
Next, add a new cell below this first one by clicking the plus (+
) symbol at the bottom left of the existing cell.
To allow information to be added into the new cell, click the small View as Markdown icon. Markdown is good for creating documentation of your code or, in this case, instructions for using it to solve a problem.
Enter the following text into your Markdown cell precisely as you see it here:
**Problem:**
Solve the following simultaneous equations using Python:
$4x + 2y = 34$
$2x - y = 31$
As you type in the text, you’ll see the formatted version appear above where you’re typing. There’s no need to run a Markdown cell manually to see its output.
Much of what you type appears as plain text. However, you use some Markdown syntax to add formatting. To bold the word Problem:
, you enclose it with double asterisks (**
). To make both equations look more mathematical, you wrap them with dollar signs ($
). Note that Markdown doesn’t recognize a new line, so you separated each line with a blank line to space out the wording the way you want it to appear.
If you’ve typed in the code correctly, then your Markdown cell will look like this:
If your output looks different from this, closely examine the text you typed and ensure it’s precisely what you intended to type.
Next, you’ll solve both equations using the Python NumPy library.
Add a new cell to your notebook. When you add a new cell, it’ll be a code cell by default. Now, type the following into this third cell:
import numpy as np
coefficients = np.array([[4, 2], [2, -1]])
results = np.array([34, 31])
solution = np.linalg.solve(coefficients, results)
Your code will solve the simultaneous equations. You need to create arrays to use NumPy to solve a pair of simultaneous equations. Your first array, named coefficients
, is a two-dimensional array consisting of the coefficients of both equations. In other words, the constants which multiply the x
and y
variables. Your second two-dimensional array, named results
, contains the results of each equation.
You use the solve()
function from NumPy’s linalg
linear algebra module to solve the equations. You pass both of your arrays into solve()
and receive another back, which you’ve named solution
, that contains the solution to the equations.
You could have seen the solution by adding solution
as the last line of code, and marimo would have printed array([12., -7.])
above your code cell. The first element tells you that x = 12, while the second tells you that y = -7. Unfortunately, this approach isn’t very user-friendly, and there’s a better way.
Insert another code cell into your notebook. Within this cell, you’ll display the solution to your quadratic equation formatted using markup. To do this, type the following code into the new code cell, then run it:
mo.md(
f"""
The solution to these simultaneous equations is:
**x = {solution[0]}**
**y = {solution[1]}**
"""
)
Because this is a code cell, not a Markdown cell, you must run it to see its output.
Note: There’s an important difference between using a Markdown cell, and using Markdown within mo.md()
. You can only type Markdown into Markdown cells. The output will appear automatically as you type. In contrast, you use mo.md()
within a code cell to embed Python code within Markdown. You must run code cells to see their output.
You pass in an f-string containing the content you want to be displayed into marimo’s md()
function. This function allows you to combine programming code with Markdown and format your output.
Most of what you pass to md()
is text. However, by enclosing the solution[0]
and solution[1]
code within curly braces ({}
), you define code to be run. As before, you use double asterisks (**
) to apply bold formatting to the solution.
In this case, you display the first and second elements of your solution
array containing the answers. The result is now the more readable version shown below:
Again, if you see something different, then check your code carefully.
To round off this introduction, you’ll see why marimo is called a reactive notebook.
Taking a First Look at Reactivity
Earlier, you learned that marimo is an example of a reactive notebook. This means that their cells react to changes made in other cells. For example, if you change the value of a variable in your notebook, all other cells using that variable will be automatically rerun and updated. If you’ve ever used a spreadsheet package such as Microsoft Excel, then you’ve already seen something similar.
To see marimo’s reactive capabilities in practice, update the highlighted code in the third cell of your simultaneous_equations.py
notebook to look like this:
import numpy as np
coefficients = np.array([[4, 2], [2, -1]])
results = np.array([50, 23])
solution = np.linalg.solve(coefficients, results)
Now, run only that cell by clicking the yellow Run button in the top right of the cell. When you run the cell, your new code runs as expected. Take a look at the bottom cell in your notebook, and you’ll see that the output there has also changed:
Although you did not run this cell manually, marimo ran it automatically because the code in this cell depended on the earlier code change you made. This cross-cell awareness is why marimo is said to be a reactive notebook. Unfortunately, the earlier pure Markdown cell didn’t change because pure Markdown cells are not reactive. For completeness, you might like to update it yourself manually.
This tutorial provides many more examples of the power of reactivity. This first example was just a taster of what’s to come.
You’ve finished this notebook, but there’s still lots more to learn. Your simultaneous_equations.py
notebook has already been saved for you. To close it, click the red X icon at the top right of the notebook, then select Shutdown.
Next, you learn how reactivity gives marimo its power.
How Marimo’s Reactivity Supports Reproducibility
Although cross-cell awareness is the core concept of reactivity, and the previous example of simultaneous equations illustrates its central point, reactivity also supports notebook reproducibility. This allows you to distribute your notebooks to others and be confident that the results they see are what you intend them to see.
In this section, you’ll learn how reactivity supports reproducibility.
Adding Code in an Unexpected Order
Your next notebook will allow you to calculate the length of a right-angled triangle’s longest side, or hypotenuse.
This example is designed to show you that cell order in marimo doesn’t matter. However, arranging cells in this way is considered bad practice. Although the notebook will always produce the expected results even if it were distributed to another user, inserting code in a random order makes your notebook challenging to read.
When working with marimo, as elsewhere with Python, it’s best to place your imports at the beginning of your code, and define your functions and variables before they are first used.
Use the following code to get your new notebook started:
$ marimo edit hypotenuse_calculator.py
As before, follow the steps outlined below carefully. Some of these steps will appear counterintuitive if you’ve used linear notebooks previously, but follow them anyway. As you’ll soon see, marimo knows what it’s doing even if it appears that you don’t.
Click inside the first notebook cell and type the code shown below into this cell. The Run icon will turn yellow to show that the cell needs to be run, but don’t run the code just yet:
def calculate_hypotenuse(opposite, adjacent):
return math.sqrt(opposite**2 + adjacent**2)
Your calculate_hypotenuse()
function calculates the hypotenuse of a right-angled triangle with two side lengths passed as its opposite
and adjacent
parameters. It uses the exponentiation operator (**
) to square both lengths before adding them and finally uses math.sqrt()
to produce the answer. In other words, it uses Pythagoras’ theorem, which you may remember from school.
Now, perform these steps to complete your notebook:
-
Add three new cells to your notebook by clicking the New cell below icon three times.
-
Insert the variable declarations
opposite = 3
into the first of these cells,adjacent = 4
into the second, then the function callcalculate_hypotenuse(opposite, adjacent)
into the third blank cell. Again, don’t run any of these cells just yet. -
Finally, although this may seem strange, add another cell below the four existing cells and insert
import math
into it. This will allow marimo to use the available built-inmath
module. Don’t run this cell, either.
Before you go any further, make sure your notebook looks like this:
As you look through each code cell, you might be wondering why you placed the import math
code in a cell long after it’s been needed by your calculate_hypotenuse()
function. Although the import should have been placed at the top of the notebook for readability, you put it here to learn that marimo isn’t as concerned about cell order as you usually are.
You can also see that because you’ve not run the cells yet, their Run icons are showing as stale, and no output has been calculated. At this point, you could click each of the run icons from first to last in sequence. However, this would generate an error because marimo would attempt to access the math.sqrt()
function before the import math
code was reached. But marimo has a trick up its sleeve.
Look down toward the bottom right of your notebook, and you’ll see a single yellow Run button indicating staleness. This button allows you to run all stale cells. Click it now, and you’ll get a surprise:
As you can see, your notebook has successfully calculated the hypotenuse to be 5
units without raising any exceptions. Marimo has somehow managed to make everything work.
Furthermore, if you distribute your notebook to someone, they would get the same results regardless of how the cells are ordered. The notebook is guaranteed to be reproducible. This doesn’t happen with poorly ordered cells in a linear notebook.
But how can this be possible? Is it magic? Alas, not quite. However, all will be revealed next!
Working Out the Cell Running Order
When you clicked the Run all stale cells button, you told marimo to use its lazy mode to run the cells. Despite the derogatory name, lazy mode is one of marimo’s most powerful features.
When you run a notebook in lazy mode, each cell is analyzed to determine whether it depends on any other cells and whether any other cells are dependent upon it. Marimo then uses this information to decide a running order for the cells. No cell can run unless everything it depends upon has already been run earlier, and any cells that depend upon others will be scheduled to run afterward.
Mathematically, marimo forms a directed acyclic graph, or DAG, which organizes each cell into a specific order so that flow is always linear from an independent cell to a dependent cell, with no loops. Linear notebooks don’t do this—instead, they process cells in the order in which they’re defined.
The calculation of the directed acyclic graph, which determines the correct cell running order, is why you can add cells to your notebook in any order you please without causing any problems.
If you want to see the results of marimo’s internal DAG, you can use the Explore dependencies tool. You’ll find its icon toward the top left of your notebook:
Unfortunately, the display quality within marimo isn’t very clear, so the image you see above has been doctored for display purposes. As you can see, it shows how your notebook has re-organized its cells based on how they relate to one another.
If you look at cell-0 in the screenshot, you’ll see that it depends on cell-4, as indicated by the arrow pointing toward it from cell-4. In addition, cell-3 is dependent on cell-0, as well as cell-1 and cell-2. In other words, cell-3 can only run after cell-0, cell-1, and cell-2 have all run successfully.
Similarly, cell-0 can’t run until cell-4 has been completed. This latter cell must be run first despite being defined last. Remember that this cell contains the import math
code.
Feel free to explore the Vertical Tree and Mini Map display options. Also, try clicking on the cell markers themselves. For example, click cell-0, and you’ll see it requires the math
module as input before it can run and produce the calculate_hypotenuse()
function as output. You can explore the others in the same way.
Once you’ve finished exploring the dependency information, click the small X above the Horizontal Tree view icon to close it and return to your notebook.
Next, you’ll learn more about how marimo cells react to change.
Exploring Further Change Reactions
When you use a notebook for research and experimentation, you’ll frequently want to see output for various inputs. In this section, you’ll see how marimo reacts to updates in cells. First, you’ll see what happens when you try to update a variable by redefining it later in the notebook.
Add another cell at the bottom of your hypotenuse_calculator.py
notebook, by clicking the plus (+) symbol as before. Then, in this new cell, assign adjacent
an updated value of 10
using the code adjacent = 10
. Not unexpectedly, the cell’s run icon has turned yellow. Now, look at what happens when you run the cell using either of the run buttons, which are currently stale. As you can see, things haven’t exactly gone as planned:
You can’t run this cell because you’ve already defined the adjacent
variable earlier in the notebook in cell-2. Unlike in linear notebooks, when you update a variable that was already created elsewhere, you can’t overwrite the earlier version.
Marimo won’t allow you to define the same variable in multiple cells because any cells depending on that variable won’t know which instance to use. Marimo couldn’t create a sensible cell running order with two conflicting cells. This would also ruin reproducibility.
Instead, suppose you need to change the value of adjacent
, or any other variable for that matter. In that case, you must either change the cell containing the original variable or delete it and re-create it in another cell.
Note: It’s possible to reassign a variable within the same cell as often as you please. This allows loop counters and running total calculations to work normally. It also means shortcut operators such as +=
and -=
can only reference variables in their local cells. If they tried to use a global variable from another cell, the reassignment would fail.
If you want to create a local variable, in other words, one that only exists within one cell, you do so by prefixing its name with an underscore (_
) character. You could, for example, use code like _opposite = 10
again and again in as many separate cells as you wish. Each cell would have its copy, but marimo would be happy with this because a version defined in one cell wouldn’t be accessible by code from any other cell.
Remove the offending cell that contains the error by clicking its red trash can icon labeled Delete. Then, update adjacent = 4
to adjacent = 10
in the original cell. Its Run icon will once again turn yellow. This time, when you run the cell, reactivity will kick in again:
You’ve probably noticed that the cell’s run icon turned white, indicating it’s no longer stale. The update has triggered reactivity in related cells because the hypotenuse’s original value has updated to approximately 10.44 from its previous value.
Although only two cells are involved here, changing a cell in your notebook could trigger a chain reaction. Any cells relying upon those cells will be updated, and any cells relying upon those updated cells will also be updated, and so on. It’s even possible that when you update a single cell in your notebook, you end up updating the entire notebook.
Next, you’ll see what happens when you delete a cell. In your notebook, select the cell where the opposite = 3
variable is assigned, then click the cell’s red trash can icon to delete it. Now, look at what happened this time:
Immediately after you delete the cell, marimo raises a NameError
exception. The cell where you call calculate_hypotenuse()
is feeling lonely because it can no longer access the opposite
variable that you’ve just deleted. The cell is no longer usable unless you either redefine the offending variable elsewhere or use the Undo cell deletion icon—the counter-clockwise arrow—at the bottom right of your notebook.
By not allowing you to declare variables twice and highlighting when deletions cause problems, reactivity once again ensures that it’s impossible to distribute a notebook that can reproduce invalid data. This can occur with linear notebooks.
Now that you’ve finished this section, you can close your notebook. To do this cleanly, resist the temptation to close the browser’s tab, but instead remember to do it the marimo way.
Look down at the right-hand side of your screen, and you’ll see a Save file icon containing an image of an old-fashioned disk. If this icon has a yellow tinge, click it to save any unsaved data. Everything will probably already be saved because marimo does this automatically for you every second by default.
Next, look for the Shutdown icon at the top-right of your screen. It’ll have a red tinge. Click on it, then click Shutdown to close the notebook. When you switch to the terminal where you started marimo, you should see that it’s returned to the command prompt and thanked you for using marimo. In addition to being powerful, marimo is also well-mannered.
By this stage, you’ve gained some experience using marimo, and you know why it’s called a reactive notebook. Next, you’ll learn about one of its coolest interactivity features: its UI elements.
How Marimo’s User Interface Elements Provide Interactivity
In the hypotenuse calculation you created previously, you hard-coded the initial values of each variable into separate cells. If you wanted to change their values, you updated the cell, and marimo reacted by recalculating their related cells. This is fine if your variables never or only rarely change, and if the notebook recipient has the programming knowledge to make these changes. But what if they don’t?
In some cases, you might want to give non-technical users of your notebook the ability to change the values being fed into a calculation to see what results their changes produce. Marimo allows users to make changes interactively using its various interactive user interface (UI) elements.
Marimo’s UI elements provide users with standard widgets, such as radio buttons, sliders, text boxes, and so on, enabling them to enter and adjust data. Once the user has made changes through a UI element, marimo will react (pun intended) by updating any related cells automatically. What’s more, using UI elements is straightforward.
Creating a Basic Dashboard
To gain experience using some of the marimo UI elements, you’ll build up a notebook that performs a break-even analysis. To begin with, you’ll write code that works out the break-even point for a fixed set of input variables. Then, you’ll add in some of marimo’s interactive elements to allow the inputs to be changed.
When your business manufactures products, your total cost of manufacturing these products consists of both a fixed cost and variable cost. Fixed costs are costs that don’t change regardless of the quantity of products you make. These include rent, salaries, and machinery. Variable costs vary with the quantity of products you make—for example, the cost of materials required to make each item.
A break-even analysis reveals the break-even point, or the quantity of units your business must sell to recover the cost of production. If the number sold is less than the break-even point, you’ve incurred a loss. However, if your sales exceed the break-even point, you’ve made a profit.
Suppose you’re in charge of a company that produces confectionery, like chocolate and ice cream. To set up your factory, you must spend $50,000. This covers the cost of your rent, machinery, and salaries. This is your fixed cost. The price to produce one box of your confectionery is $2, but you’ll sell it for $10. This $2 is your variable cost.
You decide to analyze your business using marimo, then use interactive UI elements to investigate different scenarios.
To begin with, you’ll use Matplotlib to create a plot displaying a break-even analysis.
Run these two commands in your console to both install matplotlib
and create a new notebook called break_even_analysis.py
:
$ python -m pip install matplotlib
$ marimo edit break_even_analysis.py
Once matplotlib
has been installed and you’ve run the second command, you should see the new notebook in your browser.
This time, don’t delete the empty cell at the top of the notebook. Instead, you’ll use it for your library imports to keep things neat. Type the following code into this top cell:
import marimo as mo
import matplotlib.pyplot as plt
First of all, you use import marimo as mo
to allow you access to the marimo API as before. You won’t use the API yet. You also import matplotlib.pyplot
into your notebook so you can draw the break-even plot.
Now, add a new cell and type in the following code:
fixed_cost = 50000
unit_cost = 2
selling_price = 10
upper_production_quantity = 10000
Here, you assign sample values to the four main variables used in the calculation. Although not necessary, keeping the variables together in a cell by themselves keeps things neat. Each variable is explained in the table below:
Variable | Meaning |
---|---|
fixed_cost |
The total fixed costs required for production to commence. |
unit_cost |
The cost of making one confectionery item. |
selling_price |
The selling price of one unit of confectionery. |
upper_production_quantity |
The upper bound of units you can produce. |
Later, you’ll map each of these variables to one of marimo’s UI elements to allow them to be dynamically adjusted. To begin with, you assign them specific values as test cases.
To create the plot with the information you want to see, you’ll next need to perform some preliminary calculations using the variables you created above, then draw the plot using Matplotlib.
Add another cell to your notebook and type in this code:
1break_even_quantity = fixed_cost / (selling_price - unit_cost)
2break_even_income = break_even_quantity * selling_price
3
4units = range(0, upper_production_quantity + 1, 1000)
5total_costs = [(unit * unit_cost) + fixed_cost for unit in units]
6sales_income = [unit * selling_price for unit in units]
7
8plt.plot(units, total_costs, marker="o")
9plt.plot(units, sales_income, marker="x")
10
11plt.xlabel("Units Produced")
12plt.ylabel("($)")
13plt.legend(["Total Costs", "Total Income"])
14plt.title("Break-Even Analysis")
15
16plt.vlines(
17 break_even_quantity,
18 ymin=100,
19 ymax=break_even_income,
20 linestyles="dashed",
21)
22
23plt.text(
24 x=break_even_quantity + 100,
25 y=int(break_even_income / 2),
26 s=int(break_even_quantity),
27)
28
29plt.grid()
30plt.show()
In lines 1 and 2, you perform the calculation that will reveal the break_even_quantity
and the break_even_income
. These tell you how many units you must produce to make neither a profit nor a loss, and the income you’ll receive if you make this quantity. You’ll need these figures to help you construct the plot.
Line 4 defines a Python range object, while lines 5 and 6 define two Python lists, each created with a list comprehension:
units
contains the range of quantities you might consider producing. Its default is10000
, meaning your plot will display quantities between zero and ten thousand.total_costs
contains your total costs for producing each quantity defined byunits
. This includes both your fixed and variable costs.sales_income
contains the income you’ll receive for each quantity defined byunits
. You calculate this by multiplying each quantity sold by its selling price.
You need these lists to store the data that you want to plot, which is what the remainder of your code does.
In line 8, you pass the units
and total_costs
lists into plot()
to draw the Total Costs
line on the plot. Similarly, in line 9, you pass units
and sales_income
to create the Total Income
line. You also format your plot lines using marker
parameters of o
and x
, respectively, for emphasis.
To make your plot more straightforward to understand, you use xlabel()
, ylabel()
, legend()
, and title()
in lines 11 through 14 to label the axis, add a title, and produce a legend that allows you to see what each line represents.
To highlight the result of the break-even analysis, you add a dashed
vertical line to the plot using the vlines()
function in line 16. This shows you the break-even point. You also adorn this line with the break_even_quantity
value calculated in line 1, using the text()
function in line 23 to position and display it.
Finally, in lines 29 and 30, you display a grid on the plot and the plot itself.
To view the plot, run the cell and it’ll appear as shown:
As you can see, your code has displayed a break-even quantity of 6,250 units. If you produce more than this, then you’ll make a profit, but if you produce less, you’ll make a loss. If you produce exactly 6,250 units, you’ll break even.
Of course, this result only tells you the situation for one specific set of figures. To see a range of possible answers, you could change the four key variables and rerun the cell each time to see how your changes affect the results. A far more user-friendly way is to use marimo’s user interface elements to help you adjust the values. You’ll do this next.
Adding Interactivity
Suppose you’re reviewing your business options and have come to the following conclusions:
- Your fixed costs of $50,000 could be trimmed to $40,000 if you cut back elsewhere in the business.
- You could alter the cost price of your confectionery in increments of $1, between $2 and $5.
- You could change the selling price of your products to any value.
- You also want to ensure the plot can display production quantities of up to 15,000 units.
To allow you to experiment with each of these combinations and see their effects on the break-even point, you’ll add some of marimo’s UI elements into your notebook. They’re all within its extensive user interface elements library.
In this example, you’ll use four different UI elements to give you an idea of what’s available. Once you complete this part of the tutorial, you’ll have the skills to work with any of the UI elements.
Adding the UI Elements
If you were starting from scratch with an empty notebook, then you’d need to locate the cell at the top of your notebook that contains the marker code import marimo as mo
. Remember, this is a blank cell, so this code isn’t present. To use marimo’s UI elements, you need to type this code into a cell. To follow good practice, you type it into this first cell:
import marimo as mo
This allows you to use the marimo API with its conventional alias of mo
.
Next, you need to add some code to alter the fixed costs dynamically to $40,000 or $50,000. One way to do this is to use a radio
element, which produces a set of mutually exclusive choices.
Add another new cell into your notebook below the cell that contains the plot code, then add the following:
ui_fixed_cost = mo.ui.radio(options=["40000", "50000"], value="50000")
First, you create a Python list containing two strings. These will form the option labels next to your radio buttons. You’ll be able to choose either one of them.
You create the actual radio buttons using mo.ui.radio()
. You pass a list of possible choices to its options
parameter and set the initial value to $50,000 by passing 50000
to its value
parameter. This means that $50000
will be selected by default. You also assign the radio buttons to the ui_fixed_cost
variable. This is necessary to allow your plotting code to access their values, as you’ll see later.
Next, you must alter the confectionary’s cost price in $1 intervals between $2 and $5. You use a mo.ui.slider()
element for this task. The slider allows you to select a value by sliding a little dot along a trough with your mouse.
To create the slider, add the following code to the same cell below your radio buttons:
ui_unit_cost = mo.ui.slider(start=2, stop=5, step=1)
You create the slider element by passing in start
and stop
values to slider()
. These indicate the lower and upper values you can select. In addition, the step=1
parameter allows you to change the slider in increments of 1. In other words, you may use it to select either $2, $3, $4, or $5. This time, you assign the slider()
to the ui_unit_cost
variable.
Next, you want to be able to adjust the selling price to any value. To do this, you use a mo.ui.text()
element. This provides a text box to type in any desired value. Just ensure you enter a number. Otherwise, your code will crash since you won’t implement error handling in this exercise.
To create the text box, add the following code to the same cell, on a line below your slider:
ui_selling_price = mo.ui.text(value="10")
This time, you decide on value="10"
, which gives the text element a default selling price of $10. Text boxes allow you to enter anything you like, as you’ll see later. You assign the text element to the ui_selling_price
variable.
The final adjustment you want users to make is to set the plot’s width to display production quantities of up to 15,000 units. You could do this using a mo.ui.dropdown()
element, which provides an expandable list of values from which you select one.
To define your final element, add the following code to your cell:
ui_quantity = mo.ui.dropdown(
options={"10000": 10000, "12000": 12000, "15000": 15000},
value="10000",
)
You create the items you want users to choose from by assigning a dictionary to the options
parameter of mo.ui.dropdown()
. Each dictionary key defines the items you want to be selectable, while the values are what gets returned. The default value is "10000"
, so it’ll return 10000
. Although the keys are strings, the values, in this case, are integers, which is what you want. It’s irrelevant here, but the keys and their values could differ.
Displaying the UI Elements in Your Notebook
To make the various user interface elements available, you must display them in your notebook. You can do this using the mo.md()
function you saw earlier. To see this working, enter the following code into your cell below your UI definitions:
mo.md(
f"""
Fixed Costs: {ui_fixed_cost}
Unit Cost Price: {ui_unit_cost}
Selling Price: {ui_selling_price}
Maximum Production Quantity: {ui_quantity}
"""
)
As before, you pass mo.md()
an f-string, allowing you to embed code within Markdown. Here, your code is the UI element variables you defined above. Again, you refer to them from within curly braces ({}
). When you run this cell, the UI elements you created will be shown in a neatly formatted layout:
If your output differs, review the code, correct any typos, and rerun its cell.
As you can see, the four UI elements you defined are all displayed, although they’re not yet functional. While you can certainly click, type, and slide the UI elements, your plot won’t react to your changes, at least not yet.
Bringing the UI Elements to Life
To make the plot react to changes in the UI elements you’ve just created, you must reference the elements within the code that draws the plot. Then, each time you update an element, marimo reacts to the change by rerunning the plot’s code and displaying a revised version.
When you use a UI element, the value you set is assigned to the element’s .value
attribute. So, for example, when you select the 40000
radio button, the ui_fixed_cost.value
attribute gets assigned 40000
. To read this from within your code, you reference ui_fixed_cost.value
.
To link the UI elements to your plot’s code, you should update your plot code’s main variable assignments to look like this:
fixed_cost = int(ui_fixed_cost.value)
unit_cost = ui_unit_cost.value
selling_price = float(ui_selling_price.value)
upper_production_quantity = ui_quantity.value
Here, you assign each UI element’s .value
attribute to the corresponding break-even calculation variable.
To correctly populate the fixed_cost
and selling_price
variables, you cast them to an int
and float
, respectively. You did this because both radio buttons and text boxes return strings. These must be converted to allow the plot generation code to understand them. You didn’t cast the slider()
or dropdown()
elements because these return integers natively.
To test your efforts, you must first run the cell containing these updated variable assignments. As always, the yellow tinge on its Run button reminds you to do this. Once you’ve run the cell, you can play with the various UI elements and test your scenarios.
Your plot automatically updates each time you adjust. This occurs because each time you change a UI element, the change filters through to its associated variable, causing the plot definition cell to rerun in reaction to your change.
This is yet another example of the power of marimo’s reactivity in action. Time for a workout. It’s your turn to shine.
Testing Your Skills
Now that you’re familiar with creating a user-friendly notebook, it’s time to test what you’ve learned so far and try the following exercise:
See if you can add the following functionality to your notebook:
- Add an appropriate UI element that allows users to hide or display the break-even point and its value.
- Add an appropriate UI element that allows users to select a color for the
Total Costs
plot of red, green, or blue.
Feel free to look at the marimo documentation for help. One possible solution is included in the break_even_analysis_solution.py
notebook file you’ll find in your downloads.
Now, see if you can do this:
Open up the simultaneous_equations.py
notebook you created earlier. Now, see if you can update it to allow you to insert any coefficients you like into UI elements and display their results.
For example, your input area might look like this:
Which should produce results that look something like this:
Again, feel free to look at the marimo documentation for help. One possible solution is included in the simultaneous_equations_ui.py
notebook file in your downloads.
By now, you know that one of the strengths of marimo is its reproducibility. In the next section, you’ll learn more about how marimo supports notebook distribution. Go ahead and close any open notebooks—you’ll be creating a new one shortly.
How Marimo Makes Notebooks Distributable
Many everyday use cases for notebooks require them to be shared. Sharing notebooks does have potential problems, one of which is ensuring reproducibility. You’ve already seen how marimo is designed to support reproducibility through its reactivity, but that only solves part of the problem. In this section, you’ll learn more about how marimo makes notebooks easy to distribute.
Managing the Notebook’s Environment With Sandboxing
When you distribute a notebook, you assume its recipient knows what additional packages are required to allow your notebook to run. For example, external packages such as pandas and Matplotlib might be needed for data analysis and plotting, respectively. In addition, the notebook may require specific versions of these to run correctly on a recipient’s computer. If the packages aren’t present, the notebook can’t run.
Installing these missing packages may cause other issues. If the recipient already uses a different version of a required package, then updating it to run your notebook could break their existing code. Fortunately, marimo takes care of these environment management issues using sandboxing.
When you run a notebook in a sandbox, you do two things. First, you create a temporary virtual environment for it to run in. This is similar in concept to the Python virtual environments you may already be familiar with. If a package is installed within a sandbox, regardless of its version, it won’t interfere with anything outside that sandboxed environment.
Second, when you create a notebook in a sandbox, marimo will track the required packages and their versions and write this information into the notebook’s underlying Python file. This means that when you share the notebook, a note of the packages required to support it is also included.
Furthermore, provided the recipient runs your notebook in a sandbox, these dependent packages will be installed automatically into a virtual environment before the notebook’s code runs. Once the recipient closes your notebook, its sandbox and installed packages disappear. So, once you’ve created your notebook, you can quickly and safely share it with others.
Because sandboxing isn’t enabled by default, you must do so manually using the --sandbox
flag as follows:
marimo edit --sandbox notebook.py
: Allows you to create a new notebook or edit an existing one in a sandbox. It also checks the required dependencies and offers to install them if they’re absent. What’s more, marimo continues to maintain package tracking as you update the notebook.marimo run --sandbox notebook.py
: Allows you to open an existing notebook in a sandbox, install the required dependencies, and run the notebook in read-only mode.marimo new --sandbox
: Allows you to create a new notebook in a sandboxed environment.
Note: To use marimo’s sandboxing feature, you must first install the uv package manager. You do this using python -m pip install uv
, which marimo needs to install packages.
Next, you’ll learn how to use sandboxes.
Practicing Sandboxing
In this section, you’ll create a new notebook within a sandbox. You’ll see how this affects its underlying code, then run it in a second sandbox to simulate a notebook’s distribution in a remote user’s environment.
To begin with, run the following commands in your terminal to install the uv
package manager and create a new notebook called packages.py
within a sandbox:
$ python -m pip install uv
$ marimo edit --sandbox packages.py
Before you type any code into your notebook, take a quick look at the packages that are already available to your notebook by clicking the Manage packages icon as shown below:
Although marimo comes preinstalled with several packages, pandas isn’t one of them. Next, you’ll write some pandas code, but you won’t install pandas just yet.
By default, marimo will install packages using pip
. To register packages within a sandbox, you must instruct marimo to use uv
. To set this globally, select the Settings icon at the top-right of your screen, then choose User Settings and Package Management. Finally, set the Manager as uv
. Click the X to close this window. The screenshot below illustrates this:
Once you’ve set up uv
as the package manager, you can type the code shown below into the first cell of your notebook, still without installing pandas:
import pandas as pd
data = {"rank": [1, 2, 3], "language": ["Python", "Java", "JavaScript"]}
languages = pd.DataFrame(data)
languages
Your code creates and displays a simple pandas DataFrame. Now run your notebook cell. The cell doesn’t run but has raised a ModuleNotFoundError
exception instead. As you can see from the traceback below the cell, it can’t find pandas. Fortunately, help is at hand:
Go ahead and click the Install button to install the latest version of pandas into your sandbox using uv
. Once the installation is complete, the cell will now run cleanly. If you click the Manage packages icon twice, you’ll see pandas added to the list of installed packages.
When you installed pandas, because you’ve enabled sandboxing, details of the pandas installation were written into the notebook’s underlying Python file in line with PEP 723 guidelines. If you open packages.py
in your favorite Python editor and take a look at the top of the file, you’ll see something like this:
packages.py
# /// script
# requires-python = ">=3.13"
# dependencies = [
# "marimo",
# "pandas==2.2.3",
# ]
# ///
Because sandboxing is being used, marimo has written details of the additional packages it needs at the top of the file. This means that when you distribute your notebook, the recipient doesn’t need to know which additional packages are required, or install them in advance, because marimo already knows what it needs. Note that your version numbers may be different from those shown.
Note: It’s best if the recipient of the notebook installs uv
in their environment to ensure the packages the notebook requires won’t interfere with any existing ones they may already be using.
You can simulate distributing your notebook elsewhere by opening it on your computer. First, make sure to deactivate any virtual environment you might have activated. Then, run the marimo run packages.py
command. You’ll be asked if you want the notebook to Run in a sandboxed venv containing this notebook’s dependencies. Answer Y(es) to this question.
Once marimo has started, you only need to wait a short time for the packages to be installed. Your notebook will open in read-only mode because you used marimo run
instead of marimo edit
. Then, once its cells have run, you’ll see the results. When complete, pandas will have been installed into the sandbox without interfering with any other Python environment you may already have.
Of course, distributing notebooks in this way is fine if your end user is familiar with them. However, this isn’t always the case. You’ll see how marimo copes with this problem next.
Exporting a Notebook
Sometimes, you may want to distribute your notebook to someone who doesn’t use marimo or has never had the pleasure of using Python. Fortunately, marimo allows you to export your notebook in various formats. Suppose, for example, you want to download your notebook as an HTML file.
First, you run the entire notebook and make sure that you can see its results. Then, you select the icon containing three lines in the top-right corner of your screen as shown below:
Next, select Download, then Download as HTML. When you look in your browser’s download folder for the HTML file, you’ll see it named after your notebook, only with an .html
extension.
Unfortunately, your UI elements won’t work. However, if you click the Run or edit this notebook button at the top-right corner of your browser screen, you’ll get some advice for running it online using WebAssembly. Feel free to explore this if it’s of interest to you.
Note: In addition to using the graphical user interface (GUI) to export notebooks, you can also export them using commands. For example, marimo export html notebook.py -o notebook.html
will perform the same export you performed above. The documentation shows that marimo’s export capabilities are extensive.
Next, you’ll learn how to run the underlying marimo Python file as a Python script.
Running a Notebook as a Script
As you know, marimo notebooks are Python files. They don’t use special formats other than a few decorators for their internal use. This means that you can run a notebook file in the same way you run any other Python script file.
You might be wondering about code order. If your notebook contains code that’s out of order, marimo can still handle it, but can such a file be run as a script? After all, the order of Python code is essential. You’ll investigate this next.
Adding Some Explanatory Markdown
Close any open notebooks and create a fresh one named quadratic.py
. You don’t need to use a sandbox for this example. Type import marimo as mo
in the top cell to ensure Markdown works. Then, look for the row of icons below your notebook’s cell and click the one labeled Markdown. This will add a new cell for note-taking.
Now, very carefully, type the text below into the cell:
A quadratic equation is one of the form **$ax^2 + bx + c = 0$**,
where $a$, $b$, and $c$ are constants, and $a \neq$ 0.
You can solve it using the _quadratic formula_:
$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
For example, suppose you wanted to solve: **$2x^2 - 3x - 2 = 0$**
Remember, unlike Python cells, you don’t need to run Markdown cells because they update themselves as you type. If you’ve typed everything correctly, the output part of your cell should look like the screenshot below. If not, see if you can fix it before you go any further:
Much of what you see in the output is what you typed into the cell. However, you’ve also added some Markdown formatting and LaTeX to it.
Markdown is a markup language for creating formatted text using a text editor. If you look at the two quadratic equations in your output, they display in bold because you surrounded them with a pair of double asterisks (**). In other words **$ax^2 + bx + c = 0$**
. To italicize the quadratic formula words, you surround them with a single underscore (_).
You produce the mathematical content of your output using LaTeX. You type $ax^2 + bx + c = 0$
to create the quadratic equation in the first line. By surrounding it with dollar signs ($), you give it a mathematical appearance. You also use dollars for the a
, b
, and c
constants and the inequality at the end of the sentence for the same reason. Try removing the dollar signs, and you’ll see the difference they make.
Most of the characters in your LaTeX equation display directly on the screen, albeit using an algebraic format. For example, the 𝑥
character is somewhat rounded. You use the caret (^
) symbol to display exponentiation, so the 2
, indicating squaring, appears in superscript. Toward the end of the first sentence, you use \neq
within $a \neq 0$
to produce the inequality symbol commonly used in mathematics.
You use the central piece of LaTeX, $$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
, to display the quadratic formula. This time, because you want to display the formula in a line by itself, you enclose it within a pair of dollar signs ($$
).
You also use \frac
to display the equation as a fraction so that its numerator, that’s everything enclosed within the outer pair of curly braces ({}
), appears in a line above its denominator. You then define the denominator, {2a}
, using a second pair of curly braces next to those of the numerator. To display the plus-or-minus symbol, you use \pm
, while \sqrt
gives you a square root symbol around the b^2 - 4ac
characters within the numerator’s inner curly braces.
The main point of this section is to see how a notebook containing poorly ordered cells will cope when it’s run as a script. Now that your reader knows what they’ll be doing, you can add the Python code to solve your quadratic equation.
Add seven new code cells into your notebook below the Markdown cell and enter the following code into them:
# Cell 3
x1 = (-b + math.sqrt(b**2 - 4 * a * c)) / (2 * a)
# Cell 4
x2 = (-b - math.sqrt(b**2 - 4 * a * c)) / (2 * a)
# Cell 5
a = 2
# Cell 6
import math
# Cell 7
b = -3
# Cell 8
c = -2
# Cell 9
print(f"x = {x1} and {x2}.")
Once you’ve completed your coding, cells three through nine in your notebook should look like this:
You add code to your notebook’s third and fourth cells to calculate the two possible values of a quadratic equation.
You insert variable declarations into the fifth, seventh, and eighth cells, while the sixth cell is where you import the built-in math
module. There’s no need to install this module since it ships with Python.
Then, you use the ninth cell to call another built-in function, the print()
function, to print the quadratic equation’s solutions. Although this print()
isn’t necessary in the notebook, you’ll need it when you run your code as a script.
Feel free to run your notebook in marimo. Even though you created the cells out of order, marimo has sorted them out for you as you expected. The solutions are x = 2.0 and -0.5.
With all the coding complete, you’re ready to create the script.
Creating the Script
In the terminal where your quadratic.py
file is saved, run the following command:
$ marimo export script quadratic.py -o equation.py
This will extract your quadratic.py
notebook Python file into an equation.py
script. Now run it and see what happens:
$ python equation.py
x = 2.0 and -0.5.
As you can see, everything has worked perfectly. None of the Markdown has appeared, but the results have. How can this be, given that the calculation code is out of order?
Quick! Open equation.py
in your favorite Python code editor and you’ll find out:
equation.py
__generated_with = "0.13.6"
# %%
import marimo as mo
# %%
mo.md(
r"""
A quadratic equation is one of the form **$ax^2 + bx + c = 0$**,
where $a$, $b$, and $c$ are constants, and $a \neq$ 0.
You can solve it using the _quadratic formula_:
$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$
For example, suppose you wanted to solve: **$2x^2 - 3x - 2 = 0$**
"""
)
# %%
a = 2
# %%
import math
# %%
b = -3
# %%
c = -2
# %%
x1 = (-b + math.sqrt(b**2 - 4 * a * c)) / (2 * a)
# %%
x2 = (-b - math.sqrt(b**2 - 4 * a * c)) / (2 * a)
# %%
print(f"x = {x1} and {x2}.")
The file contains Python code. Marimo uses special comments (# %%
) to separate the code into different cells.
Everything runs perfectly because when it created the script, marimo reordered the code into a sensible running order, in the same way as it does when you run unordered cells in marimo directly. When you look down through the code, you see that the import is placed toward the top, and all variables are declared before they’re used. Python was quite happy to run your code without issue and return the correct result.
Note: One of the other advantages of marimo notebooks is that because they’re Python files, you’re simply changing Python code whenever you make a change. This makes them very easy to track on version-tracking systems such as Git or GitHub. Although other notebook formats can be stored on these systems, because such notebooks use JSON or a proprietary format, changes are much harder to track and manually see.
To wrap up, you’ll revisit these concepts in a Jupyter Notebook to highlight the advantages of reactive notebooks.
Why Linear Notebooks Don’t Quite Cut It Anymore
In this final section, you’ll learn about some of the problems associated with linear notebooks. Although this section won’t teach you more marimo techniques, it’ll make you appreciate why reactive notebooks outperform their linear ancestors.
You’ll need Jupyter Notebook installed on your computer to recreate these examples. To recreate the problems highlighted, you should also have a basic knowledge of Jupyter. Alternatively, you can just read the section passively.
Redefinitions
When you run code in a linear notebook such as a Jupyter Notebook, the code for each notebook is run in a process called its kernel. The kernel is where your notebook stores its variables, function definitions, and any other objects it needs to run—in other words, the program’s state. Unfortunately, this architecture produces something called the hidden state problem.
To understand the problems the hidden state can cause, look at the screenshot below. It shows a series of cells in a Jupyter Notebook. The numbers in square brackets ([n]
) down the left side show the order in which the cells were run—in other words, linearly. The yellow disks on the right are used to reference the cells from within this tutorial:
The code is similar to the Pythagoras code you used in an earlier notebook you created in this tutorial. If you want to recreate your version of this notebook, you can do so by typing up the code as shown. Alternatively, you could use the hidden_state.ipynb
file you’ll find in your downloads.
Once you have your notebook, you can ensure everything runs linearly by selecting the Kernel menu, then choosing Restart Kernel and Run All Cells. You should see the output shown above.
In the first cell, the one annotated with the yellow number one, you import the built-in math
module to allow you access to its sqrt()
function.
In the second cell, you then define calculate_hypotenuse()
, which calculates the hypotenuse of a right-angle triangle when you pass its opposite
and adjacent
side lengths. The function uses the exponentiation operator (**
) to square each length, and math.sqrt()
to find the square root of the sum of these squares. This is the hypotenuse.
In the third and fourth cells, you define the initial lengths for a triangle’s opposite
and adjacent
sides. You calculate the hypotenuse of your triangle by calling calculate_hypotenuse()
in cell five. The answer is 5.0 units.
Now, take a careful look at cells six and seven. In cell six, you redefine the value of opposite
as 5
before using cell seven to recalculate the updated hypotenuse. To calculate, you use your new opposite
value of 5
and your existing adjacent
value of 4
. This value is still available because it’s stored inside your notebook’s kernel.
You then run another version of the calculation by updating adjacent
to 12
in cell eight, and calling calculate_hypotenuse()
again. This time, your function uses the latest adjacent
value of 12
plus the most recent opposite=5
value defined in cell six. The hypotenuse now has a length of 13
.
Although everything works as expected, your notebook contains historical data that may confuse you. When you update opposite
from 3
to 5
in cell six, even though cell seven produces a correctly updated hypotenuse when it’s run, the output produced by the original running of cell five doesn’t change. It still retains the previous value of 5.0
.
While you may be happy with this as a historical record, sometimes it’s preferable that updates in one cell automatically update related cells to keep things consistent.
Fortunately, this problem can’t exist in marimo for two reasons. First, marimo doesn’t allow you to declare the same variable in two different cells. Second, marimo’s reactivity means any changes in one cell automatically trigger updates elsewhere.
Cell Running Order
You may also encounter the hidden state problem when cells run out of order. To see the effect of this, consider this updated version of your notebook:
In this version of your notebook, you’ve still defined cells six and seven in their original order. However, you’ve run them in reverse order. You can do this for yourself by performing the following steps:
-
From the JupyterLab or Jupyter Notebook menu, choose the Kernel menu, then select Restart Kernel and Clear Outputs of All Cells. This will clear all existing content, providing a fresh base from which you can work.
-
Select the cell marked with the yellow one and run it. Then repeat for cells marked with the yellow two, three, four, and five. Because you’ve run these cells in their original order, the results will be the same as earlier.
-
Now run the cell marked with the purple seven, then run the cell marked with the purple six. Note that you’re running these out of order.
-
Next, conveniently forget to run, in other words ignore, the cell marked with the green eight.
-
Finally, run the cell marked with the yellow nine.
If you’ve done this correctly, your results should match those in the screenshot. If not, perhaps you’ve missed a step. You’ll need to restart the kernel and clear everything once more.
When you run cells out of order in a linear notebook, you can cause problems. Look at the output of cell six. You’ll see it’s incorrect. It hasn’t changed from the previous output you obtained when you ran cell five. You still use the original opposite=3
and adjacent=4
values. The opposite=5
value wasn’t used because it only existed in the kernel when you ran cell seven, after you ran cell six.
A problem like this can make notebooks very difficult to debug and share. You usually run linear notebooks from start to finish without missing any cells. If you sent your linear notebook to someone else, then they might get a different result than you intended when they run it.
In its current form, your notebook isn’t reproducible unless you provide instructions on how others should run it. If you’ve genuinely forgotten to run one or more cells, you may even have problems reproducing your earlier results in the future. Furthermore, debugging this problem can be difficult when large notebooks are involved.
More confusion occurs when you run cell eight. Although this cell now uses opposite=5
, because you forgot to run the cell that set adjacent=12
, the original adjacent=4
value was again used. Anyone casually reading your notebook would likely think the adjacent=12
was being used.
Why not rerun the notebook linearly to see the confusion? Choose Kernel, then Restart Kernel and Run All Cells. You should see the original results, which are what others will see if you don’t explain to them what the correct running order should be.
Although it’s possible in a marimo notebook to run cells individually in any order you please and even ignore some of them, neither of the above problems can occur if you run all cells in your notebook. This will highlight any duplicate variable declarations and reorder cells into a single, correct running order for you.
Expired Data Retention
Another issue with the kernel is that it still stores content even after you delete the cells defining it. So, if you were to delete a cell containing a variable assignment or a function definition, cells using these would still function because although they’re unseen, they’re still in the kernel. That’s why you should always restart the kernel after changing anything in a linear notebook. That will clear out expired data permanently.
To see this problem, take a look at the screenshot below:
The upper left-hand screenshot shows a new Jupyter Notebook being run for the first time. You can simulate this with one of your existing notebooks by choosing Run … Run All Cells from the menus. As you can see, all cells are run in order and produce the expected output.
The lower right-hand screenshot shows the same notebook, only with the upper two cells removed. Once again, choosing Run … Run All Cells generates the same result.
This can also confuse anyone reading the notebook because it wouldn’t be immediately clear what’s happening based on what’s available. Furthermore, the next time the kernel is restarted or the notebook is passed to another person, it would fail to run because neither the import nor calculate_hypotenuse()
code would be present.
As you can see, although retaining variables in the kernel allows cells in a notebook to be run without error, their results may not be what you expect. It creates an illusion of incomplete code that’s working mysteriously correctly. This would cause a problem for anyone receiving your notebook.
Marimo solves this problem using reactivity. It makes sure that when a cell, or even the content within a cell, is deleted, any cells depending on those deletions raise an error.
Conclusion
By now, you not only know how to create a marimo notebook, but also how it can decide for itself the order in which its code should run. You’ve seen various examples of the benefits reactivity brings to your notebook when you alter its cells, add common user-interface elements, or even add confusing duplicate code.
Additionally, you’ve seen how marimo supports notebook distribution through sandboxing and how to convert notebooks into alternative formats for use outside the marimo environment. You also understand that the problems associated with traditional notebook environments don’t exist in marimo.
In this tutorial, you’ve learned:
- How to create a marimo notebook
- What’s meant by reactivity
- How marimo makes sense of unordered code cells
- How to add interactivity to your notebook
- How to distribute notebooks easily and safely
- The inherent flaws of linear notebooks
Although you’ve had a thorough learning experience with marimo, there’s still lots to learn. You’re encouraged to review the official marimo user guide to learn more.
There are also some tutorials available within marimo itself. To see the current list, type marimo tutorial --help
into your terminal. If you see a tutorial you fancy, then type in marimo tutorial
, followed by the name of the tutorial you wish to work through.
The marimo developers have also made several example notebooks available for you to learn from.
Frequently Asked Questions
Now that you have some experience with marimo notebooks in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
You create a new marimo notebook by using the command marimo edit <notebook_name>.py
in your terminal.
In a reactive notebook, cells automatically update when you change dependent variables, while in a linear notebook, you must manually rerun cells in the correct order.
Marimo uses a directed acyclic graph (DAG) to determine the correct running order for cells, ensuring the notebook runs correctly even if the cells are added in an unexpected order.
Marimo’s sandboxing feature creates a temporary virtual environment for your notebook, ensuring that the required packages are isolated and don’t interfere with other projects.
You add interactivity to a marimo notebook by using marimo’s user interface elements, like sliders and radio buttons, to allow users to adjust data and see the results immediately.
Take the Quiz: Test your knowledge with our interactive “Marimo: A Reactive, Reproducible Notebook” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Marimo: A Reactive, Reproducible NotebookThis quiz is a great way to reinforce and build on what you've learned about marimo notebooks. You'll find most of the answers in the tutorial, but you'll need to do some additional research to find some of the answers.