Keeping Your Streamlit App in Sync: Mastering st.session_state for Multi‑Step Workflows
Learn how to use Streamlit’s st.session_state to keep data across reruns, build multi‑step forms, and avoid common pitfalls like memory bloat. A clear example, trade‑offs, and a quick checklist make it easy to implement in your next app.
02 Oct 2025, 09:56 UTC

Why State Matters in Streamlit
When you build a Streamlit app, the entire script runs from top to bottom every time a user interacts with a widget. That means local variables are re‑initialized on each rerun, and any data you want to keep must be stored somewhere else. st.session_state is Streamlit’s answer to this problem: a per‑user, per‑session dictionary that survives script reruns. It lets you build multi‑page wizards, persist form input, and share data between components without resorting to external databases.
How st.session_state Works Under the Hood
When a user first visits the app, Streamlit creates an isolated session object. Each widget that receives a key argument automatically writes its value into st.session_state[key]. During a rerun, Streamlit restores the session object, so the dictionary still contains the values the user entered earlier. Because the session lives on the server, it is isolated per browser tab and per user; opening the app in a new tab creates a new session.
Key points:
- It is dictionary‑like:
st.session_state['count'] = 5 - Widgets can read from it via the same key.
- Callbacks (
on_change,on_click) can trigger logic immediately when a widget updates the state.
Building a Multi‑Step Form with Session State
Below is a minimal example that walks through a three‑step data entry process. Each step is shown in its own st.expander, and the user can’t proceed until the current step is complete. The state dictionary keeps track of the current step and the values entered so far.
# app.py
import streamlit as st
# Initialize state variables if they don’t exist
if 'step' not in st.session_state:
st.session_state.step = 1
if 'name' not in st.session_state:
st.session_state.name = ''
if 'age' not in st.session_state:
st.session_state.age = None
# Helper to update step
def next_step():
st.session_state.step += 1
st.title('Three‑Step Registration')
# Step 1: Name
with st.expander('Step 1: Enter your name', expanded=st.session_state.step==1):
st.session_state.name = st.text_input('Name', value=st.session_state.name)
if st.session_state.name:
st.button('Next', on_click=next_step)
# Step 2: Age
with st.expander('Step 2: Enter your age', expanded=st.session_state.step==2):
st.session_state.age = st.number_input('Age', min_value=0, value=st.session_state.age or 0)
if st.session_state.age:
st.button('Next', on_click=next_step)
# Step 3: Review
if st.session_state.step==3:
st.success('All data collected!')
st.write('Name:', st.session_state.name)
st.write('Age:', st.session_state.age)
if st.button('Reset'):
st.session_state.clear()
st.experimental_rerun()
**Verification** – Run the app, fill in the fields, and press Next. The values persist, and the final review step shows the collected data. If you press Reset, st.session_state.clear() empties the dictionary and st.experimental_rerun() restarts the script, giving you a clean slate.
Trade‑Offs and Practical Limits
While st.session_state is powerful, it’s not a silver bullet:
- Memory footprint – The session lives on the server. Storing large dataframes or images can quickly exhaust RAM, especially with many concurrent users.
- Isolation – Data is not shared across users or tabs. If you need cross‑user persistence, you must push data to a database or cache.
- Debugging complexity – Because the script reruns on every interaction, the flow of execution can become non‑linear. Overusing global state may make it harder to trace bugs.
A common pattern to mitigate memory usage is to store only lightweight identifiers (e.g., a row ID) in the session, then fetch the heavy data from a database when needed.
Actionable Checklist for Your Next Streamlit App
- Identify the data that must survive across interactions.
- Give widgets a
keyand link them tost.session_state. - Use callbacks to trigger logic immediately after a state change.
- Keep the session payload small; offload heavy objects to external storage.
- Test by manually resetting the session (e.g., via a
Resetbutton) to ensure the UI returns to its initial state.
By following these steps, you’ll build Streamlit apps that feel responsive, maintain state reliably, and scale responsibly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.