Skip to main content

Module 1 — First steps and the Streamlit execution model

Streamlit lets a Python author turn a script into a web application without touching HTML, JavaScript or any front-end framework. The promise is real, but it rests on one design decision that surprises every newcomer: the entire script is re-executed from top to bottom every time the user interacts with the page. Understanding that single fact prevents about 80 % of the questions asked on the community forum, and it shapes every module of this course.

Installation and the first run

Streamlit is a plain PyPI package. A virtual environment is strongly recommended: the framework pins recent versions of pandas, numpy, altair and pyarrow, and mixing those with an older global environment is a recipe for import-time surprises.

# terminal
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install streamlit==1.36.0 pandas scikit-learn joblib
streamlit --version

A first application takes seven lines:

# app.py
import streamlit as st

st.title("Churn dashboard")
st.write("Hello, sales team.")
name = st.text_input("Customer name")
if name:
st.success(f"You typed {name}.")

Launching is a single command, and it opens a browser tab on http://localhost:8501:

# terminal
streamlit run app.py

That command starts a small Tornado server, which serves the HTML shell once, then talks to the browser through a websocket. Every widget your script creates has an identity on that channel, and every user gesture sends a message back to the server that triggers the piece of behavior described in the next section.

The rerun-on-interaction execution model

Type a letter in the text_input widget and Streamlit does not call a callback attached to that widget. It re-executes app.py from the top to the bottom, with name now equal to the current text. st.title runs again, st.write runs again, st.text_input runs again — and its return value is your new letter. The old page is diffed against the new one, only what changed is sent over the wire.

This model has three consequences that beginners repeatedly stumble on.

First, top-level code is not a one-time setup. If you write counter = 0 at the top of the file and then increment it on a button click, counter will be reset to zero on the very next interaction. State that must survive reruns belongs in st.session_state, covered in module 6.

Second, expensive work runs on every interaction unless you cache it. A model loaded with joblib.load("model.pkl") on line 3 will be reloaded from disk every time the user moves a slider. On a 200 MB model that is seconds of lag per keystroke, and it is one of the most common performance bugs of new Streamlit apps. Module 5 introduces @st.cache_data and @st.cache_resource for exactly that reason.

Third, the order of widgets defines their identity. Streamlit identifies a widget by its position and its label; renaming a label or inserting a widget above it resets the widget's state. Passing a stable key="customer_name" argument decouples identity from position and is a habit worth adopting early.

What a rerun does not do

A rerun re-executes Python, not the browser tab. The HTML shell, the CSS theme, the websocket and the browser's scroll position stay in place. Two things you might expect to happen do not:

  • Random state is fresh. Calling random.random() at the top of your script gives a new value on every rerun. If you want a stable random choice, seed it (random.seed(42)) or store the drawn value in st.session_state.
  • Print statements go to the terminal, not to the page. Streamlit writes them to the process's standard output, which is invisible in the browser. Use st.write, st.dataframe or the sidebar for on-page logging.

To force a rerun from code, call st.rerun(). To stop the current run early — for example after a validation error — call st.stop(). Both are useful and both live in the standard library of Streamlit gestures.

The first page of the running example

Enough theory. Here is the first version of the churn dashboard the sales team will use. It has one widget, no cache and no state yet, but it already shows the top-to-bottom flow at work.

# app.py
import streamlit as st
import pandas as pd

st.set_page_config(page_title="Churn dashboard", page_icon="📉", layout="wide")

st.title("Churn dashboard")
st.caption("Sales team — internal use only")

st.header("Score one customer")
tenure = st.slider("Months as a customer", min_value=0, max_value=72, value=12)
monthly_charge = st.number_input("Monthly bill (USD)", min_value=0.0, value=70.0, step=1.0)
contract = st.selectbox("Contract type", ["Month-to-month", "One year", "Two years"])

if st.button("Score this customer"):
# A placeholder rule that returns a probability between 0 and 1.
base = 0.5 - 0.005 * tenure + 0.002 * monthly_charge
penalty = {"Month-to-month": 0.15, "One year": 0.0, "Two years": -0.10}[contract]
probability = max(0.0, min(1.0, base + penalty))
st.metric("Estimated churn probability", f"{probability:.0%}")

Two conventions in this snippet are worth pointing out. st.set_page_config must be the first Streamlit call in the script; putting it anywhere else raises a StreamlitAPIException. And the if st.button(...) block only enters the branch on the rerun triggered by the click itself: on the very next rerun caused by, say, moving the slider, the button returns False again and the metric disappears. Persisting the result across reruns is the job of st.session_state in module 6.

Read the sidebar in the browser

The little menu in the top right corner of every Streamlit page exposes "Rerun", "Clear cache" and "Settings". "Clear cache" is the fastest way to recover from a stale @st.cache_data result during development, and knowing it exists saves a lot of confused restarts.

In summary

  • Streamlit re-runs your script top to bottom on every interaction; design against that fact, do not fight it.
  • Top-level variables are not persistent; state that must survive belongs in st.session_state (module 6) and expensive work belongs behind a cache (module 5).
  • st.set_page_config must be the first Streamlit call; widget identity is derived from label and position, so pass a stable key= as soon as your layout might change.
  • streamlit run app.py starts a small server and a websocket; print statements go to the terminal, only st.* calls appear in the browser.

Next module: input and display components, the vocabulary from which the scoring form of this course is built.