Streamlit Session State - Interview Questions
1. What is st.session_state and why do you need it?
Streamlit re-runs the script top-to-bottom on every interaction. Local variables are reset each run. st.session_state is a dict-like object that persists across reruns within the same browser session.
import streamlit as st
if "count" not in st.session_state:
st.session_state["count"] = 0
if st.button("Increment"):
st.session_state["count"] += 1
st.write("Count:", st.session_state["count"])
Without session state, the counter would reset to 0 on every rerun.
Scope: per browser tab. Two tabs = two separate sessions. Closing the tab clears it. There is no cross-user or persistent storage — for that, use a database.
2. How do you read and write session state?
Two equivalent syntaxes:
# Dict-style
st.session_state["user_id"] = 42
uid = st.session_state["user_id"]
# Attribute-style (only for valid Python identifiers)
st.session_state.user_id = 42
uid = st.session_state.user_id
# Safe read with default
n = st.session_state.get("count", 0)
# Init pattern
st.session_state.setdefault("history", [])
Standard dict operations work: in, .get(), .pop(), iteration.
3. How do widgets interact with session state?
Every widget with a key parameter automatically writes its value to st.session_state[key]:
name = st.text_input("Name", key="user_name")
# After render, st.session_state["user_name"] holds the current value.
# `name` and `st.session_state["user_name"]` are the same value.
You can also seed a widget by setting state before the widget is created:
if "user_name" not in st.session_state:
st.session_state["user_name"] = "Alice"
st.text_input("Name", key="user_name")
You cannot assign to st.session_state[key] after the widget with that same key has rendered in the same run — Streamlit raises an error. Either set it before the widget, or use a callback.
4. What are on_change callbacks?
Widgets accept an on_change callback that fires before the rerun, useful for side effects or for syncing state across widgets:
def on_select():
st.session_state["selection_changed"] = True
log.info(f"Selected: {st.session_state['kind']}")
st.selectbox("Kind", ["A", "B", "C"], key="kind", on_change=on_select)
Callbacks run in the order: on_change fires → script reruns. Pass arguments via args=(...) or kwargs={...}.
on_click is the same idea for buttons.
5. How do you reset state or “clear the form”?
# Reset specific keys
for key in ["name", "age", "email"]:
if key in st.session_state:
del st.session_state[key]
st.rerun()
# Reset everything
st.session_state.clear()
st.rerun()
Note: deleting a widget’s key resets that widget on the next render only if the widget is re-created. If you want a “Clear” button that resets a form, the standard pattern is:
if st.button("Clear"):
st.session_state.pop("name", None)
st.session_state.pop("age", None)
st.rerun()
6. What is st.rerun() and when do you call it?
st.rerun() (formerly st.experimental_rerun) immediately stops the current run and starts a new one from the top.
Use cases:
- Inside a callback that mutated state and wants the UI to reflect it on the next render.
- After a form submit when the next render needs different layout.
- After login: store the token, rerun, render the authenticated app.
Most of the time you don’t need it — Streamlit reruns automatically on widget changes. Reach for st.rerun() only when you’ve changed state programmatically (not via a widget) and want an immediate refresh.
if st.button("Login"):
token = authenticate(...)
st.session_state["token"] = token
st.rerun() # next run sees the token, shows the app
7. What’s the difference between widget value, widget key, and session_state?
value = st.text_input("Name", key="user_name", value="default")
- Return value (
value) — the current widget value, available immediately in this run. key="user_name"— the slot inst.session_statewhere Streamlit writes/reads it. With akey, the widget is bound to that slot.value="default"— the initial value used the first time, before the user touches it. Ignored if a value already exists in session state for that key.
After the widget renders, value == st.session_state["user_name"]. They’re the same data, accessed two ways.
8. How do you share state across pages in a multipage app?
st.session_state is shared across pages of the same Streamlit app — you don’t need any extra plumbing.
# page_1.py
st.session_state["user_id"] = login()
# page_2.py
uid = st.session_state.get("user_id")
if uid is None:
st.warning("Please log in.")
st.stop()
State persists as long as the browser tab stays open. Closing the tab or hitting “Rerun” cold (Ctrl-Shift-R) clears it.
For cross-session persistence (user closes tab, comes back), write to a DB or set cookies via a component like streamlit-cookies-controller.
9. Common session_state pitfalls
| Pitfall | Fix |
|---|---|
| Setting state for a widget after the widget renders → error | Set state before the widget, or use on_change callback |
| Expecting state to persist after browser refresh | It doesn’t. Use a DB or cookies |
| Two tabs of the same app share state | They don’t. Each tab is its own session |
| Mutating a list/dict in state and not seeing the change | The reference is the same; mutation works. If it doesn’t, you’re probably reassigning a local variable that shadows the state entry |
| Callback not firing | on_change fires only when the widget value actually changes, not on every rerun |
st.button value not surviving |
Buttons are momentary; persist the click into session state |
| Race conditions between callback writes and widget writes | Callbacks run before the rerun completes — set non-widget state in callbacks; let widgets manage their own keys |
10. Cross-links
- Widget basics and
st.form: 01_basics_and_widgets.md - When state needs to persist beyond the session (DB): ../../08_databases/
- LLM chat history in session state: 05_llm_chat_apps.md
Interview angle
- “What is
st.session_state?” - a per-session dict surviving reruns. It’s the only way to keep state, since local variables are recreated on every run. - “How long does it live?” - for the browser session. It’s not persistent storage and it’s not shared between users, so anything that must outlive a refresh belongs in a database.
- “What’s the widget-key interaction?” - a widget with
key="x"reads and writesst.session_state["x"], so you can set a widget’s value programmatically by assigning to it before the widget renders.