Optimizing Streamlit Apps with @st.cache_data
Learn how to use @st.cache_data to eliminate redundant computations in Streamlit apps, including practical examples of TTL configuration and the critical difference between data and resource caching.
14 Jan 2026, 02:26 UTC

Solving the Rerun Performance Bottleneck
Streamlit operates on a unique execution model: every time a user interacts with a widget, the entire Python script reruns from top to bottom. While this simplifies development, it creates a performance bottleneck when your app performs expensive operations—such as querying a large SQL database, loading a 500MB CSV, or running a complex pandas transformation—on every single click.
The solution is @st.cache_data. This decorator tells Streamlit to store the return value of a function in a local cache. On subsequent reruns, if the input parameters haven't changed, Streamlit skips the function execution entirely and returns the cached result instantly.
How @st.cache_data Works
When you wrap a function with @st.cache_data, Streamlit creates a hash of the function's code and its input arguments. If both the code and the arguments match a previous execution, the cached value is retrieved. If either changes, the function executes again and the cache is updated.
Implementation Example: Cached Data Loading
Below is a practical implementation for loading a dataset and performing a heavy calculation. This example assumes you are using Streamlit version 1.18.0 or newer.
import streamlit as st
import pandas as pd
import time
# This function is cached. It will only run once unless 'url' changes.
@st.cache_data
def load_large_dataset(url):
# Simulate a heavy network request or computation
time.sleep(3)
df = pd.read_csv(url)
return df
# This function is cached with a Time-To-Live (TTL) of 1 hour
@st.cache_data(ttl=3600)
def process_data(df, threshold):
# Simulate a heavy pandas transformation
time.sleep(2)
return df[df['value'] > threshold]
# App Logic
DATA_URL = "https://example.com/large_data.csv"
user_threshold = st.slider("Select Threshold", 0, 100, 50)
# First run: takes ~3 seconds. Subsequent runs: near-instant.
data = load_large_dataset(DATA_URL)
# First run: takes ~2 seconds. Changes to slider trigger a rerun of this function.
filtered_data = process_data(data, user_threshold)
st.write(filtered_data)
Configuration Breakdown
ttl(Time-To-Live): Specified in seconds. This is critical for data that updates periodically (e.g., a stock price API). In the example above,ttl=3600ensures the cache expires every hour.- Hashing: Streamlit hashes the function body. If you change a line of code inside the cached function, Streamlit detects the change and invalidates the cache automatically.
Critical Distinctions: Data vs. Resource
A common engineering mistake is using @st.cache_data for everything. Streamlit provides two distinct caching decorators based on what you are storing:
| Feature | @st.cache_data | @st.cache_resource |
|---|---|---|
| Best For | Dataframes, lists, strings, JSON | DB connections, ML models, API clients |
| Behavior | Returns a copy of the cached data | Returns a reference to the same object |
| Risk | Memory overhead if datasets are massive | Thread-safety issues if object is mutated |
Limitations and Common Pitfalls
Mutable Object Corruption
Because @st.cache_data returns a copy of the data, it prevents most state-sharing bugs. However, if you use @st.cache_resource for a database connection and then modify that connection's state globally, every user session will see that change. Always use @st.cache_data for the results of a query, and @st.cache_resource for the connection object itself.
Non-Hashable Inputs
Streamlit must be able to hash the arguments passed to the function. If you pass a complex custom object that doesn't support hashing, Streamlit may throw an error or fail to cache correctly. For these cases, consider passing a unique ID (like a string or integer) to the function instead of the object itself.
Verification and Testing
To verify that your caching is working as intended, follow these steps:
- Initial Load: Run the app. Note the time it takes for the first load (e.g., 5 seconds).
- Rerun: Interact with a widget. The app should reload almost instantly, indicating the cached result is being used.
- Parameter Change: Change the input parameter (e.g., move the slider). The specific cached function associated with that parameter should execute again, while other cached functions (like the initial data load) remain instant.
- Cache Clear: Use the "Clear Cache" option in the Streamlit app menu (top right) to force a full refresh of all cached functions.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.