Streamlit Caching - Interview Questions
1. Why does Streamlit need caching?
Streamlit re-runs the entire script on every interaction. Without caching, every button click would re-load the CSV, re-query the database, re-fit the model. Caching is how you make a Streamlit app fast.
Two decorators:
@st.cache_data— for data (DataFrames, lists, dicts, JSON, numbers, strings — anything serializable).@st.cache_resource— for resources (database connections, ML models, HTTP clients — singletons that should not be copied).
Older @st.cache is deprecated; don’t use it in new code.
2. What is @st.cache_data?
Caches the return value of a function. Streamlit hashes the arguments, looks up the result, and returns a copy so callers can’t mutate the cache.
@st.cache_data
def load_data(path: str) -> pd.DataFrame:
return pd.read_csv(path)
df = load_data("sales.csv") # first call: reads CSV
df = load_data("sales.csv") # second call: returns cached copy
Use it for: file reads, API calls returning JSON, expensive transformations, query results.
Don’t use it for: anything you can’t pickle (open file handles, DB connections, models with non-serializable internals).
3. What is @st.cache_resource?
Caches a single shared instance — no copying, no hashing of return value. All callers get the same object.
@st.cache_resource
def get_db_connection():
return psycopg.connect(DATABASE_URL)
@st.cache_resource
def load_model():
return SentenceTransformer("all-MiniLM-L6-v2")
conn = get_db_connection() # same conn across all reruns and all users
model = load_model() # loaded once per process
Use it for: database connections, ML models in memory, HTTP session objects, anything where “create once, share forever” is the right semantics.
Critical: the cached object is shared across all users hitting this Streamlit process. Don’t put per-user state in here.
4. When do you use cache_data vs cache_resource?
| Question | Answer | Decorator |
|---|---|---|
| Returns a DataFrame, list, dict, scalar? | Yes | cache_data |
| Returns a DB connection / model / client? | Yes | cache_resource |
| Should each caller see an independent copy? | Yes | cache_data |
| Must all callers share the same instance? | Yes | cache_resource |
| Object can be pickled? | Yes | cache_data |
| Object can NOT be pickled? | Then | cache_resource |
Mnemonic: data = “I want a copy.” resource = “I want the original.”
If you wrap a DB connection in @st.cache_data, you’ll either crash on pickling or return a copy that points to a closed socket. If you wrap a DataFrame in @st.cache_resource, mutating it in one callback corrupts every other user’s view.
5. How does Streamlit hash the arguments?
Streamlit hashes function arguments to look up the cache. Built-in types (str, int, float, list, dict, DataFrame, …) hash automatically.
For unhashable types or custom objects, prefix the argument name with _:
@st.cache_data
def fetch(_session: requests.Session, url: str) -> dict:
return _session.get(url).json()
Underscore-prefixed args are excluded from the hash. Use this for objects you can’t or don’t want to hash (clients, connections passed in).
You can also pass hash_funcs={SomeType: lambda x: x.id} to customize hashing per type.
6. How do you set TTL or invalidate the cache?
@st.cache_data(ttl=3600) # 1 hour
def load_prices():
return fetch_from_api()
@st.cache_data(ttl="10m", max_entries=100)
def query(symbol: str):
return db.query(symbol)
ttl— seconds, timedelta, or string ("10m","1h").max_entries— LRU eviction past this count.
Manual invalidation:
load_prices.clear() # clear this function's cache
st.cache_data.clear() # clear all cache_data caches
st.cache_resource.clear() # clear all cache_resource caches
Add a “Refresh” button:
if st.button("Refresh"):
load_prices.clear()
st.rerun()
7. What is show_spinner?
@st.cache_data(show_spinner=True) shows a “Running …” spinner during cold cache misses. Defaults to True. Pass a string for a custom message, or False to suppress:
@st.cache_data(show_spinner="Loading sales data...")
def load_sales():
...
@st.cache_data(show_spinner=False)
def quick_lookup(k):
...
Disable for fast lookups where the spinner just flashes annoyingly.
8. Common caching mistakes
# Mistake 1: caching a function that mutates external state
@st.cache_data
def log_query(q):
db.execute("INSERT INTO log VALUES (?)", q) # only runs on cold cache!
# Mistake 2: cache_data on a connection
@st.cache_data # WRONG — connection is not picklable / will be copied
def get_conn():
return create_engine(DSN)
# Mistake 3: cache_resource on per-user data
@st.cache_resource # WRONG — shared across all users
def user_dashboard(user_id):
return load_user_data(user_id)
# Mistake 4: caching the result, then mutating it
@st.cache_data
def load():
return pd.read_csv("a.csv")
df = load()
df.drop(columns=["x"], inplace=True) # cache_data returns a copy, so this is fine
# but with cache_resource it would corrupt other callers
Rule: cache pure functions. Side effects don’t belong inside a cached function.
9. How does caching interact with st.session_state?
They serve different scopes:
- Cache is per-process, shared across all sessions and users hitting this Streamlit instance.
st.session_stateis per-session (per browser tab).
Use cache for “expensive to compute, same answer for everyone” (loaded model, lookup table). Use session state for “this user’s current selection.”
Caching the result of a user-specific query? Either include the user id in the cache key (which works but pollutes the cache across users) or just put it in session state.
10. Cross-links
- Execution model context: 00_streamlit_overview.md
- Per-session state: 02_session_state.md
- DB connection patterns: ../../08_databases/
Interview angle
- “
st.cache_dataorst.cache_resource?” -cache_datafor serialisable return values, returning a copy per call so mutation can’t corrupt the cache.cache_resourcefor unserialisable shared objects - database connections, ML models - returned by reference and shared across sessions. - “What happens if you use the wrong one?” - caching a connection with
cache_datatries to serialise it and fails or misbehaves; caching a DataFrame withcache_resourceshares one mutable object across all users. - “How is the cache key computed?” - from the function’s arguments and code. Unhashable arguments need an underscore prefix to be excluded, and
ttlbounds staleness.