Persisting Widget Values in Streamlit with session_state: A Practical Guide
Learn how Streamlit’s session_state keeps widget values persistent across reruns, with a hands‑on example, trade‑offs, and best‑practice tips for building stateful apps.
11 Sept 2026, 07:32 UTC

Why session_state Matters
Every Streamlit app reruns from top to bottom whenever a widget changes. That means the only way a widget can remember what the user did is if the value is stored somewhere that survives the rerun. session_state is Streamlit’s built‑in solution: a global dictionary attached to each user’s session that is automatically serialised to the client’s browser via a session cookie. It lets you keep expensive computations, form data, or shared flags alive without writing to a database or a file.
How Streamlit Implements It
When you first reference st.session_state, Streamlit creates a dictionary for that user. Each key/value pair is JSON‑serialisable and is sent back to the client on every rerun. On the next interaction the server restores the dictionary from the cookie, so your code can read or modify it as if it were a normal Python variable.
Because the state lives on the client, it is isolated per user. Two people opening the same app will never see each other’s values unless you explicitly share data on the server side.
A Hands‑On Example
Below is a minimal app that demonstrates how a button click updates a counter stored in session_state. The counter is read by a second widget that shows the current value.
import streamlit as st
# Initialise the counter only once per session
if "counter" not in st.session_state:
st.session_state.counter = 0
# Button to increment
if st.button("Increment"):
st.session_state.counter += 1
# Display the current value
st.write(f"Current count: {st.session_state.counter}")
Steps to verify:
- Run the app with
streamlit run app.py. - Click Increment a few times; the number should increase.
- Refresh the browser; the count should stay the same.
- Open a private‑incognito window and run the same app; the counter starts at 0 again, proving per‑user isolation.
Trade‑offs & Pitfalls
- JSON‑serialisable only: Storing a pandas DataFrame, a database connection, or a file handle will raise an error. Keep heavy objects in a separate cache (e.g.,
@st.experimental_memo) and store only lightweight flags insession_state. - Client‑side storage: Large data structures increase page load time and browser memory usage. If you need to share large data across users, use a server‑side cache or database.
- Stale data: Caching expensive functions in
session_statewithout an invalidation strategy can serve outdated results after code changes. Combinesession_statewith@st.experimental_memoand clear the memo when necessary. - Per‑user isolation: If your app needs shared state (e.g., a global counter), you must implement it on the server side; otherwise each user will see an isolated copy.
Actionable Take‑aways
• Initialise session_state keys once per session to avoid key‑error crashes.
• Use session_state for lightweight flags, form values, or memoised results.
• Combine with @st.experimental_memo for heavy computations that should not run on every rerun.
• Keep objects JSON‑serialisable; otherwise move them to a separate cache or re‑initialise them per session.
• Test your app by refreshing the page and by opening multiple browser windows to confirm isolation.
With these practices, Streamlit’s session_state becomes a powerful tool for building responsive, stateful web apps without the overhead of external persistence layers.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.