In TanStack Query v5, what is the difference between staleTime and gcTime, and what are their default values?
answer
- two clocks, two different jobs
- fresh versus kept in memory
- one starts at fetch, one at unmount
- zero versus five minutes
- cacheTime was renamed in v5
basics
~20 sstaleTime (default 0) sets how long fetched data counts as fresh, skipping mount, focus and reconnect refetches; gcTime (default 5 minutes, formerly cacheTime) sets how long a query with no mounted observers stays in memory before deletion.
solid answer
~40 sIn TanStack Query v5 the two options run on different clocks. `staleTime` starts when data is written and defaults to `0`, so data is stale immediately; while data is fresh, a new mount, a window refocus or a reconnect reads the cache without refetching. `gcTime` only starts once the last observer unsubscribes and the query becomes *inactive*; it defaults to 5 minutes (`Infinity` during server rendering), and when it expires the entry is deleted. So stale data is still served instantly and refetched in the background, while collected data means a hard loading state on the next mount. v5 renamed `cacheTime` to `gcTime` because people read it as "how long data is cached", when it does nothing while a query is in use.
code
ts · 10 linesimport { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
gcTime: 10 * 60 * 1000,
},
},
})go deeper
Recall the two names and their defaults: staleTime 0 governs freshness, gcTime of five minutes governs how long unused data stays in memory.
Explain the two clocks: staleTime counts from the last write, gcTime only from the moment the last observer unsubscribes, and walk through fresh, stale, inactive and deleted.
Show that you tune staleTime per kind of data to cut refetches, keep gcTime at or above it, and can explain why raising gcTime never fixes a refetch complaint.
Discuss app-wide QueryClient defaults versus per-query overrides, and the memory cost of a long gcTime on data-heavy screens against instant back-navigation.
## Two options, two clocks TanStack Query keeps every query in a **query cache**, one entry per hashed `queryKey`. Two options decide what happens to an entry over time, and they answer different questions: - **`staleTime`** answers "may I serve this without asking the server again?" It is measured from the moment data was last written to the entry (`dataUpdatedAt`). - **`gcTime`** answers "may I throw this entry away?" It is measured from the moment the query lost its last **observer**, meaning the last mounted `useQuery` (or other subscriber) for that key. Because they start on different events, neither bounds the other. Data can be stale for an hour while a component keeps it on screen, and it can still be fresh while it sits unobserved, waiting to be collected. ## The defaults and what they mean | Option | Default in v5 | Starts counting when | When it runs out | |---|---|---|---| | `staleTime` | `0` | data is written to the cache | the data is **stale**; the next trigger refetches it | | `gcTime` | `5 * 60 * 1000` (5 minutes); `Infinity` during server rendering | the query has no observers left | the query and its data are **removed** from the cache | A `staleTime` of `0` means data is stale the instant it arrives. That is a deliberately aggressive default: the library assumes server data can change at any moment. `staleTime` also accepts: - a number of milliseconds, the usual choice; - `Infinity`, never stale by time, though a manual invalidation can still mark it stale; - `'static'`, never stale, and invalidation is ignored as well; - a function of the query that returns one of the above. A `gcTime` of `Infinity` disables garbage collection. When several observers of one key pass different `gcTime` values, the longest one wins. Finite values are capped in practice at roughly 24 days by the 32-bit delay limit of `setTimeout`. ## The lifecycle of one entry Follow one key through the defaults: 1. A component mounts `useQuery({ queryKey: ['todos'], queryFn: fetchTodos })`. Nothing is cached, so it shows a hard loading state and fetches. 2. The response is written to the cache. With `staleTime: 0` it is **stale** at once, but it is still what the component renders. 3. A second component mounts with the same key. It receives the cached data **immediately**, and because the data is stale, `refetchOnMount` (default `true`) starts a background refetch. Both components update when it lands. 4. Both components unmount. The query is now **inactive**: still cached, but with no observers. The `gcTime` timer starts. 5. If a component mounts again within five minutes, the timer is cleared, the cached data renders instantly, and a background refetch runs because the data is stale. 6. If nobody mounts within five minutes, the entry is **garbage collected**, and the next mount starts from a hard loading state again. Stale is not the same as gone. Stale data still renders; only deleted data forces a loading state. ## Why v5 renamed cacheTime In v4 the option was called `cacheTime`. The name suggested "how long data is cached", so developers raised it to make data "last longer" and were then puzzled that refetches kept happening. It never had that effect: it does nothing while a query is in use and only governs how long an **unused** entry lingers. v5 renamed it `gcTime` to say what it does. A tutorial that writes `cacheTime` is describing v4. ## Choosing values - Raise **`staleTime`** to get fewer refetches. It is the setting that suppresses mount, focus and reconnect refetches. Choose it per kind of data: seconds for a live feed, minutes for a profile, `Infinity` or `'static'` for reference data loaded once. - Raise **`gcTime`** when back-navigation should render from memory after a longer absence and the memory is affordable. - Keep `gcTime` at least as long as `staleTime`. If it is shorter, an unobserved entry can be deleted while it would still have counted as fresh, and the next mount pays for a full load anyway. - Set app-wide values once in `defaultOptions.queries` on the `QueryClient`, and override per query where the data behaves differently. ```ts import { QueryClient } from '@tanstack/react-query' export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, // fresh for one minute gcTime: 10 * 60 * 1000, // unused entries kept for ten minutes }, }, }) ``` ## Common confusions - A long `gcTime` does not stop refetches; only `staleTime` or the refetch flags do. - A stale query does not refetch on its own clock. It waits for a trigger: a new mount, a window refocus, a reconnect, an invalidation or a configured `refetchInterval`. - The `gcTime` timer never removes a query that still has an observer, however old its data is.
- If gcTime is shorter than staleTime, what happens when a component remounts after the entry was collected?The entry no longer exists, so the component starts from a hard loading state and fetches, even though the data would still have counted as fresh had it survived. `staleTime` only protects data that is still in the cache; garbage collection of an inactive entry ignores freshness entirely.
- What is the difference between staleTime: Infinity and staleTime: 'static' in TanStack Query v5?Both stop time-based staleness, so mount, focus and reconnect triggers skip the query. With `Infinity`, a manual `queryClient.invalidateQueries` can still mark it stale and refetch it. With `'static'`, invalidation has no effect and even the `'always'` setting of the three refetch flags is blocked, which suits data that cannot change while the app runs.
staleTime is the best-before date on a milk carton: past it you still pour from it, but you check whether a fresher one exists. gcTime is how long an abandoned carton stays in the office fridge after its owner stops using it before the clear-out; leaving it there longer does not change its best-before date.
saying these in an interview costs you the question
- gcTime, or cacheTime, is how long data stays fresh before a refetch.
- Raising gcTime will stop a query refetching when a component mounts.
- The default staleTime is five minutes, so fetched data stays fresh for a while.
- Once data turns stale it is deleted, and the next mount shows a loading spinner.
- The gcTime countdown starts as soon as the data has been fetched.
- cacheTime is still the v5 option name for garbage collection.