DataLens

Running apps and the rerun model

Understand the one idea that explains all of Streamlit's behaviour, the script reruns from top to bottom on every interaction, and run apps on your own computer.

Level: Foundations · 7 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

Most web frameworks make you write "when this button is clicked, update that label". Streamlit doesn't. Its model is much simpler, and slightly surprising:

Key idea

Every time a user interacts with a widget, Streamlit reruns your whole script from top to bottom, with the widget now returning its new value.

That's it. No callbacks to wire up, no update functions. Your script is a recipe for the page, and Streamlit cooks it again whenever an ingredient changes. Understanding this one rule explains nearly everything Streamlit does, including the parts that confuse beginners.

Running an app on your computer

On this site, the App tab runs your script in a simulator. On your own machine, Streamlit is a normal Python package. With uv:

uv add streamlit
uv run streamlit run app.py

Streamlit starts a small local web server and opens http://localhost:8501 in your browser. When you save app.py, the page offers to rerun; choose Always rerun and every save refreshes the app instantly. The terminal shows any errors, and so does the page itself.

Watching the rerun happen

The best way to understand reruns is to see one. This tiny app prints the exact time it ran, then asks for your name. Keep an eye on the time while you use it:

import streamlit as st
from datetime import datetime

st.title("Rerun demo")
st.write("This script ran at", datetime.now().strftime("%H:%M:%S.%f")[:-3])
name = st.text_input("Your name")
st.write(f"Hello, {name or 'stranger'}!")

Press Try it, then type a name and press Enter. The time changes: the whole script ran again, not just the greeting line. Typing into the box didn't "update the greeting"; it triggered a fresh run in which st.text_input returned your text.

Widgets return values

Because of the rerun model, a widget is just a function call that returns the current value:

import streamlit as st

discount = st.slider("Discount (%)", 0, 50, 10)
price = 320_000
st.write(f"Price after discount: Rp {price * (1 - discount / 100):,.0f}")

On the first run, the slider returns its default (10). When the user drags it to 25, the script reruns and st.slider returns 25. Your code never asks "did the slider change?"; it just uses discount like any variable.

Buttons are True for exactly one run

A button returns True only on the run triggered by its click, and False on every other run:

import streamlit as st

if st.button("Refresh data"):
    st.success("Data refreshed!")
st.slider("Unrelated slider", 0, 10, 5)

Click Refresh data: the success message appears. Now move the slider: the message disappears, because this new run wasn't caused by the button. That's correct behaviour, but it surprises people who expect the message to "stay".

The counter that doesn't count

Here's the most famous beginner bug:

import streamlit as st

count = 0
if st.button("Add one"):
    count += 1
st.write("Count:", count)

Click three times: it always shows 1. Every run starts the script from the top, so count = 0 runs again; the click adds one, and that's it. Normal variables don't survive between runs. To remember values, Streamlit has st.session_state, which gets its own module soon.

Stopping early: st.stop

Sometimes the rest of the page makes no sense until the user provides input. st.stop() ends the current run right there:

import streamlit as st

st.title("Customer lookup")
email = st.text_input("Customer email")
if not email:
    st.info("Enter an email address to start.")
    st.stop()

st.write(f"Looking up orders for **{email}**...")

Without st.stop(), you'd need to indent the rest of the app inside an if. Stopping early keeps the main code flat and readable, the same "guard clause" idea from the Python functions module.

What the rerun model means for you

ConsequenceWhat to do
Every interaction reruns everythingkeep the script fast; slow steps (loading files, queries) need caching
Normal variables reset each runstore things to remember in st.session_state
Buttons are True for one runuse them for actions; use checkboxes/toggles for on/off state
The page is rebuilt from scratchwrite the script top to bottom in the order the page should appear

The next modules cover each of these tools. For now, the important thing is the mental model: script in, page out, again and again.

Why this matters for clean code

The rerun model rewards clean, linear scripts: read inputs at the top, compute, then display. Apps written that way are easy to follow because the code order is the page order. When an app gets confusing, it's usually because it fights the model (trying to "update" things) instead of embracing it (recompute everything from the current inputs).

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

Next: Text, titles, markdown, and displaying data →