Caching with st.cache_data and st.cache_resource

Streamlit · Practical · 14 min

Without caching, every click reloads data and recomputes everything, making apps slow and expensive.

Because scripts rerun constantly, caching is essential. Streamlit offers two decorators.

`@st.cache_data` caches the return value of a function keyed by its arguments (and its source code). Use it for anything that produces data: reading files, querying a database, calling an API, transforming DataFrames.

  • Each call returns a copy of the cached value, so callers can mutate the result safely.
  • The value must be pickleable.
  • ttl="1h" expires entries, essential for data that changes; max_entries= bounds memory.
  • Arguments must be hashable by Streamlit; prefix a parameter with an underscore (_conn) to exclude it from the key.

`@st.cache_resource` caches one shared object, without copying, for things that should exist once per server: database connections, ML models, API clients.

  • All sessions get the same object, so it must be thread-safe, and mutating it affects every user.

Both caches are global: shared across all users and sessions of the app process, which makes them efficient but means never caching user-specific secrets under a key other users can hit.

Invalidate with my_func.clear() for one function, or st.cache_data.clear() for all. Editing the function's code also invalidates its cache.

Pick by the question "is this data or a connection/model?": DataFrames and results go to cache_data; live objects go to cache_resource.

Example

import time
import pandas as pd
import streamlit as st

@st.cache_data(ttl="10m", show_spinner="Loading sales...")
def load_sales(region: str) -> pd.DataFrame:
    time.sleep(2)                                  # pretend this is a slow query
    return pd.DataFrame({"region": [region] * 3, "amount": [10, 20, 30]})

@st.cache_resource
def get_model():
    time.sleep(2)                                  # e.g. load a large ML model
    return {"name": "churn-v3"}                    # one object shared by all users

region = st.selectbox("Region", ["EU", "US"])
df = load_sales(region)          # slow the first time per region, instant after
df["amount"] *= 2                # safe: cache_data returned a copy
st.dataframe(df)
st.write("Model:", get_model()["name"])

if st.button("Refresh data"):
    load_sales.clear()           # drop cached entries for this function

Remember

  • cache_data caches return values per argument set and returns copies
  • cache_resource caches one shared, uncopied object such as a connection or model
  • Use ttl for changing data and .clear() to invalidate

Watch out for

  • Using cache_resource for a DataFrame that callers then mutate, changing it for every user.

Check yourself

  1. Which decorator fits a database connection pool used by every session?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.streamlit.io/develop/concepts/architecture/caching