Using Streamlit’s @st.cache_data and @st.cache_resource to Speed Up Interactive Apps
Learn how Streamlit’s @st.cache_data and @st.cache_resource decorators prevent costly recomputations in interactive apps, with a concrete CSV‑loading example, trade‑offs, and practical verification steps.
02 Jul 2025, 16:26 UTC

Problem: Widget‑triggered reruns make your app feel slow
Every time a user moves a slider, clicks a button, or changes a select box, Streamlit reruns the entire script from top to bottom. If your script loads a large CSV, computes expensive features, or initializes a machine‑learning model inside that rerun, the user sees a noticeable lag.
Thesis: Built‑in caching decorators let you reuse results across reruns
Streamlit provides two decorators that store the output of a function so that subsequent runs with the same inputs skip the expensive work:
@st.cache_data– caches the return value (ideal for data frames, transformed features, or any pure‑function output).@st.cache_resource– caches a mutable object that should be created once and reused (e.g., a database connection, a trained model, or a TensorFlow session).
Both rely on hashing the function’s arguments to build a cache key. Unhashable types (like a file object) must be converted or excluded via the hash_funcs parameter.
Worked example: Caching a CSV load with a slider
The following snippet shows a minimal app that reads a CSV file inside a cached function. A slider triggers a rerun, but the file is read only once.
import streamlit as st
import pandas as pd
# ---- Cached data load -------------------------------------------------
@st.cache_data
def load_data(path: str) -> pd.DataFrame:
# This print will appear only once per unique path
print(f"Reading {path} …")
return pd.read_csv(path)
# ---- App layout -------------------------------------------------------
st.title("CSV explorer")
file_path = st.text_input("CSV path", value="data/sample.csv")
# Load data (cached)
df = load_data(file_path)
# Widget that causes a rerun
row_limit = st.slider("Rows to display", 5, len(df), 20)
st.dataframe(df.head(row_limit))
Where to run: Save the script as app.py and execute streamlit run app.py from a terminal with read access to the CSV file.
Required permissions: The process must be able to open the file path supplied in the widget.
Expected check: Open the terminal where you started Streamlit; you should see the Print line only the first time the app loads or when the path changes.
Risk: If the underlying CSV changes on disk while the app is running, the cached DataFrame will become stale because the decorator watches only the function arguments, not the file’s modification time.
When to use @st.cache_resource instead
For objects that are expensive to create and should be shared across reruns (and possibly across users), use @st.cache_resource. Example: loading a scikit‑learn model once and reusing it for predictions.
import streamlit as st
from sklearn.ensemble import RandomForestClassifier
@st.cache_resource
def get_model():
print("Training model …")
# In practice you would load a pretrained model here
model = RandomForestClassifier(n_estimators=10)
model.fit([[0, 0], [1, 1]], [0, 1]) # dummy fit
return model
model = get_model()
if st.button("Predict"):
pred = model.predict([[2, 2]])
st.write(f"Prediction: {pred[0]}")
Verification: The Print line appears only once, even after many button clicks, indicating the model is not recreated.
Trade‑offs and limitations
- Stale data: Because the cache depends solely on input arguments, changes in external sources (files, databases, APIs) are not automatically detected. You must manually invalidate the cache when you know the source has changed.
- Memory consumption: Large cached objects stay in RAM for the lifetime of the server process. In multi‑user deployments each user gets an isolated cache for
@st.cache_data(unless you setallow_output_mutation=Trueon a resource), which can quickly exhaust memory if many users load big data sets. - Hashing limits: Arguments that cannot be hashed (e.g., a file handle, a custom class without
__hash__) cause a hashing error unless you provide a customhash_funcsor convert them to a hashable representation (like a file path string).
Practical way to check the result: Add a temporary counter inside the cached function (counter += 1; st.write(f"Run {counter}")) and observe that the counter increments only on the first execution for a given set of inputs.
Actionable closing
- Identify the expensive, repeatable step in your script (data load, feature engineering, model init).
- Wrap it with
@st.cache_dataif it returns a pure value, or@st.cache_resourceif it returns a reusable mutable object. - Make sure all arguments are hashable; otherwise supply a
hash_funcsdictionary or convert them to strings/ints. - Test by running the app and watching for the print/log statement to appear only once per unique input set.
- If the underlying external data can change, plan a manual invalidation strategy (e.g., a button that calls
st.cache_data.clear()or a time‑based check that clears the cache when a file’s modification time differs).
Applying these patterns will keep your Streamlit app responsive without sacrificing correctness, as long as you stay aware of the cache’s limits and clear it when needed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.