TanStack Query Lifecycle: The Difference Between staleTime and gcTime

After adopting TanStack Query, Chris is puzzled.
"It said it handles caching, so why does an API request fire every time I click the window?"
Conversely, in other situations, he gets flustered: "I reopened the tab I closed a while ago, but why isn't there a loading spinner, and why is the old data still there?"
The cause of this confusion is a lack of clear understanding regarding TanStack Query's two core timers: staleTime and gcTime. While they look similar, they play completely different roles: deciding when data is "spoiled" and deciding when to "delete it from memory."
Today, we will perfectly organize the lifecycle of these two concepts, which are the most confusing aspects for React Query beginners.
1. Data Freshness: Fresh vs. Stale
In TanStack Query, data largely exists in two states.
staleTime: The Expiration Date
staleTime is the time it takes for data to transition from Fresh to Stale.
The shocking fact is that TanStack Query's default staleTime is 0ms.
In other words, the moment data is fetched, it is considered stale. That is why every time Chris focused the window or re-mounted the component, the query thought, "Uh oh, this is stale. I need to fetch it again," and sent an immediate refetch request.
// useMyQuery.ts
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
// Insist that the data is "fresh" for 1 minute.
// Within 1 minute, no API request is sent even if the component remounts.
staleTime: 1000 * 60,
});2. Memory Management: Inactive and gcTime
So, what is gcTime (formerly cacheTime)? This determines how long data remains in memory when it is not being used (Inactive).
Let's assume the user navigates away, and the component using useQuery disappears from the screen (Unmount). At this point, the data is not deleted immediately but enters the Inactive state in the cache.
gcTime: Time to Empty the Trash
The default value for gcTime is 5 minutes (300,000ms).
The phenomenon Chris experienced—"the data is still there"—occurred because even though the data was spoiled (stale), it hadn't been deleted from memory yet (within gcTime), so the library showed the cached data first.
3. Lifecycle at a Glance
Visualizing this process looks like this:
4. Real-World Strategies: How Should You Configure It?
You need strategies to adjust these two values depending on the situation.
Strategy 1: Use Defaults (staleTime: 0, gcTime: 5 min)
Strategy 2: Static Data (staleTime: Infinity, gcTime: Infinity)
// static-data.ts
useQuery({
queryKey: ['settings'],
queryFn: fetchSettings,
staleTime: Infinity, // Forever fresh
gcTime: Infinity, // Never deleted from memory
});Strategy 3: Prevent Frequent Updates (staleTime: 30 sec)
Key Takeaways
Now you can control the lifespan of your cache. However, if you manage the names (Keys) that store the cache poorly, data can get tangled, or duplicate calls can occur. What is a maintainable key management strategy?
Continuing in: “Efficient Query Key Management Strategy: Boosting Maintainability with the Factory Pattern”