Stopping the Rerun Loop: Optimizing Streamlit with @st.cache_data
Stop the frustrating lag of script reruns. Learn how to use @st.cache_data to optimize data loading and API calls in Streamlit for a snappier user experience.
15 Apr 2026, 06:07 UTC

The Performance Wall in Streamlit
Streamlit's most convenient feature is also its biggest performance bottleneck: the script reruns from top to bottom every time a user interacts with a widget. If your app loads a 100MB CSV or calls a slow REST API, a simple slider movement can trigger a multi-second delay, making the app feel unresponsive.
The solution is @st.cache_data. This decorator allows you to store the result of a heavy computation or data fetch in a local cache. When the script reruns, Streamlit checks if the function's inputs and code have changed; if they haven't, it skips the execution entirely and pulls the result from memory.
Data vs. Resources: Choosing the Right Cache
A common point of confusion is the difference between @st.cache_data and @st.cache_resource. Using the wrong one can lead to memory leaks or broken connections.
- @st.cache_data: Used for serializable data. This includes Pandas DataFrames, lists, dictionaries, and API responses. It creates a copy of the data to ensure that mutating a DataFrame in one part of the app doesn't unexpectedly change the cached version for other users.
- @st.cache_resource: Used for non-serializable global objects. This is for database connection pools, ML models (like a PyTorch or TensorFlow model), or logger instances. It returns the same object reference every time.
Implementing a Cached Data Pipeline
To implement caching, place the decorator directly above the function responsible for the heavy lifting. Streamlit hashes the function's input parameters and the internal code to determine if the cache is still valid.
import streamlit as st
import pandas as pd
import time
# This function will only run once unless the URL changes
@st.cache_data
def load_large_dataset(url):
# Simulating a heavy network request or processing task
time.sleep(5)
df = pd.read_csv(url)
return df
# User interaction triggers a rerun
data_url = st.text_input("Enter CSV URL", "https://example.com/data.csv")
with st.spinner("Loading..."):
data = load_large_dataset(data_url)
st.write("Data loaded successfully!")
st.dataframe(data)
How to verify the cache is working
- Run the app and enter a URL. You should see the spinner for 5 seconds.
- Interact with another widget (like a checkbox or slider). The app reruns, but the data appears instantly because it is served from the cache.
- Change the URL in the text input. Streamlit detects a new input parameter, invalidates the previous cache for that specific input, and executes the function again.
Trade-offs and Memory Constraints
Caching is not a "set and forget" tool; it trades CPU time for RAM. Because @st.cache_data stores results in memory, caching multiple versions of massive DataFrames can lead to OutOfMemory (OOM) errors, especially in containerized environments with strict memory limits.
Additionally, avoid caching functions with side effects. If a function writes to a database or updates a local file, that action will only occur the first time the function is called. Subsequent calls will return the cached value without executing the side effect.
Practical Limits and Maintenance
To prevent memory bloat, you can use the ttl (time-to-live) parameter. This automatically clears the cache after a set duration, which is critical for apps relying on data that updates frequently (e.g., stock prices or sensor logs).
# Clear this specific cache every hour
@st.cache_data(ttl=3600)
def fetch_api_data():
return requests.get("https://api.example.com/live").json()
If you need to manually clear the cache during development or via a button in the UI, you can call st.cache_data.clear() to wipe all cached data functions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.