DataLens

Caching with st.cache_data and st.cache_resource

Stop reloading files and re-running queries on every click by caching results, and learn which cache to use, how cache keys work and when to clear them.

Level: Intermediate · 7 min read

Practise this module

The questions for this lesson run in your browser and check your answer straight away.

The idea

The rerun model has a cost: every click runs the whole script, including the slow parts. If loading your data takes 3 seconds, every slider move takes 3 seconds. Caching fixes this: Streamlit remembers a function's result, and the next time it's called with the same arguments, returns the stored result instantly instead of running the function again.

Analogy

A cache is a sticky note on the fridge with yesterday's answer. Before you redo a long calculation, you check whether the answer to exactly this question is already written down.

st.cache_data: for data

Loading a big CSV file can take seconds, and because the whole script reruns every time a widget changes, the file gets loaded again on every single click. The fix is to put the loading in a function and mark it with @st.cache_data. Streamlit then remembers the result and hands it back instantly next time:

import streamlit as st
import pandas as pd


@st.cache_data
def load_sales(path):
    st.write("Loading data from disk...")      # visible only when the function really runs
    return pd.read_csv(path)


sales = load_sales("sales.csv")
st.write(f"{len(sales)} order lines loaded")
st.slider("Pick a number", 0, 10, 5)

On the first run you see "Loading data from disk...". Move the slider: the message disappears, because load_sales("sales.csv") was answered from the cache. Your function's body only runs when the cache doesn't have an answer.

st.cache_data is for functions that return data: DataFrames, lists, dicts, numbers, results of API calls and database queries. Each call gets a copy of the cached value, so code that modifies the returned DataFrame can't corrupt the cache.

How the cache key works

What if the function's answer depends on its arguments? Revenue by category and revenue by channel are different results. The cache handles this by storing one result per combination of arguments:

import streamlit as st
import pandas as pd


@st.cache_data
def revenue_by(column):
    sales = pd.read_csv("sales.csv")
    completed = sales[sales["status"] == "completed"]
    return completed.groupby(column, as_index=False)["revenue"].sum()


choice = st.radio("Group by", ["category", "channel", "city"], horizontal=True)
st.dataframe(revenue_by(choice), hide_index=True)

The first time each option is chosen, the function runs; after that, switching back and forth is instant. Arguments are part of the key, so pass everything the result depends on as an argument. A function that secretly reads a global variable or a widget value won't notice when that value changes, and will return stale results.

st.cache_resource: for connections and models

Some things shouldn't be copied: a database connection, a machine-learning model, an HTTP client. They're expensive to create and meant to be shared:

import streamlit as st
import sqlite3


@st.cache_resource
def get_connection():
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.execute("CREATE TABLE visits (city TEXT)")
    return conn


conn = get_connection()
st.write("Same connection object on every rerun:", id(conn))
st.cache_datast.cache_resource
Use fordata: DataFrames, query results, API responsesresources: connections, models, clients
Returnsa fresh copy each callthe same object each call
Shared between usersyes (each gets a copy)yes (the same object)
Key idea

Data you'll read and transform → cache_data. A thing you'll use (connect with, predict with) → cache_resource.

When cached data gets old

Cached results stay until the app restarts. For data that changes (today's orders), give the cache a lifetime:

import streamlit as st
import pandas as pd


@st.cache_data(ttl=600)          # seconds: refresh at most every 10 minutes
def load_orders():
    return pd.read_csv("sales.csv")


orders = load_orders()
if st.button("Refresh now"):
    load_orders.clear()           # forget cached results for this function
    st.rerun()
st.write(f"{len(orders)} rows")

(The simulator on this site doesn't expire ttl caches by time; the parameter is accepted so your code runs unchanged in real Streamlit.)

What not to cache

Why this matters for clean code

Caching rewards the clean-function habits from the Python track: a function whose result depends only on its arguments and that has no side effects is exactly what caches correctly. If a function is hard to cache, that's often a sign it's doing two jobs (loading and logging, computing and displaying). Split it, cache the pure part.

Recap

Practise this module

The questions for this lesson run in your browser and check your answer straight away.