RTK Query vs React Query — Deep Comparison
This document compares Redux Toolkit Query and React Query from a systems, performance, and architecture perspective
Table of Contents
Part 1: RTK Query vs React Query
- What They Are
- Core Philosophy Difference
- Mental Model Comparison
- Setup & Boilerplate
- Caching Strategy
- Refetching Behavior
- Mutations & Cache Invalidation
- DevTools & Debugging
- TypeScript Experience
- Performance Characteristics
- Bundle Size & Complexity
- When to Use RTK Query
- When to Use React Query
- Interview Summary
Part 2: Debugging Redux Performance Issues
- How Redux Causes Performance Issues
- Unnecessary Re-renders
- Missing Memoized Selectors
- Overusing Global State
- Large, Monolithic Slices
- Referential Equality Issues
- Incorrect useCallback / memo Usage
- Redux DevTools Profiling
- Normalization Problems
- Middleware Overhead
- When Redux Is NOT the Problem
- Senior Interview Takeaway
- Final Verdict Summary
What They Are (High-Level)
-
RTK Query
- A data fetching and caching layer built into Redux Toolkit
- Treats server data as part of the Redux store
-
React Query
- A standalone async state manager
- Treats server data as external cached state
Core Philosophy Difference (Very Important)
RTK Query
"Server state should live inside Redux, alongside client state."
React Query
"Server state is NOT app state — it's a cache."
This single distinction drives all architectural decisions.
Mental Model Comparison
| Concept | RTK Query | React Query |
|---|---|---|
| Ownership | Redux store | Internal cache |
| Server data | Global Redux state | External cache |
| Reducers | Yes | No |
| Actions | Yes | No |
| Middleware | Yes | No |
| Redux dependency | Mandatory | None |
Setup & Boilerplate
RTK Query Setup
const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
endpoints: (builder) => ({
getUsers: builder.query<User[], void>({
query: () => "/users",
}),
}),
});
Requires:
- Adding reducer
- Adding middleware
- Redux store setup
React Query Setup
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>;
Minimal setup, framework-agnostic.
Caching Strategy
RTK Query
-
Cache stored in Redux
-
Keyed by endpoint + serialized arguments
-
Cache lifecycle controlled via:
keepUnusedDataForrefetchOnMountOrArgChange
keepUnusedDataFor: 60;
React Query
- Cache stored internally
- Keyed by query keys
- More granular cache controls
useQuery({
queryKey: ["users", id],
staleTime: 5 * 60 * 1000,
});
Refetching Behavior
| Trigger | RTK Query | React Query |
|---|---|---|
| Window focus | Configurable | Default enabled |
| Network reconnect | Configurable | Default enabled |
| Component remount | Optional | Optional |
| Manual refetch | Yes | Yes |
React Query has more aggressive freshness strategies by default.
Mutations & Cache Invalidation
RTK Query (Tag-Based)
providesTags: ["User"];
invalidatesTags: ["User"];
- Declarative
- Centralized
- Safer for large teams
React Query (Query Keys)
queryClient.invalidateQueries(["users"]);
- Flexible
- Powerful
- Easier to misuse
DevTools & Debugging
RTK Query
- Redux DevTools
- Actions, reducers, state snapshots
- Time-travel debugging
React Query
- Dedicated React Query DevTools
- Cache status
- Background refetch visibility
TypeScript Experience
| Aspect | RTK Query | React Query |
|---|---|---|
| Type inference | Strong | Strong |
| Boilerplate | Medium | Low |
| API typing | Centralized | Per hook |
RTK Query is preferred in strictly typed Redux codebases.
Performance Characteristics
RTK Query
- Redux subscription-based updates
- Can cause re-renders if selectors are not scoped
- Predictable but global
React Query
- Component-level subscriptions
- Highly optimized
- Fewer unintended re-renders
Bundle Size & Complexity
| Factor | RTK Query | React Query |
|---|---|---|
| Bundle size | Larger (Redux included) | Smaller |
| Concept count | High | Moderate |
| Learning curve | Steep | Gentle |
When to Use RTK Query
Choose RTK Query if:
-
App already uses Redux
-
You need:
- Centralized data logic
- Strong conventions
- Enterprise-scale consistency
-
Multiple teams share APIs
-
Debugging & predictability matter more than speed
Common in: fintech, dashboards, enterprise products
When to Use React Query
Choose React Query if:
-
You don't need Redux
-
App is API-heavy
-
You want:
- Minimal boilerplate
- Best-in-class caching
- Excellent performance
-
You treat backend as source of truth
Common in: startups, consumer apps, Next.js apps
Interview Summary (One-Liner)
RTK Query is a Redux-first server-state solution, while React Query is a cache-first async state manager.
Debugging Redux Performance Issues
This section focuses on real-world Redux performance debugging, not theoretical advice.
How Redux Causes Performance Issues
Redux itself is fast — performance problems usually come from:
- Too much global state
- Poor selector usage
- Over-rendering components
- Deeply nested state
- Incorrect memoization
1. Unnecessary Re-renders (Most Common)
Problem
const state = useSelector((state) => state);
This subscribes the component to entire store.
Fix
useSelector((state) => state.auth.user);
Always select the smallest possible slice.
2. Missing Memoized Selectors
Problem
const filtered = useSelector((state) =>
state.items.filter(...)
);
This recalculates on every render.
Fix
const selectFilteredItems = createSelector(
[selectItems],
(items) => items.filter(...)
);
Memoization prevents unnecessary recalculations.
3. Overusing Global State
Smell
- UI flags in Redux
- Modal open/close
- Input values
Rule
If state is used by one component, it does NOT belong in Redux.
Use:
useStateuseReducer
4. Large, Monolithic Slices
Problem
state.app = { auth, user, settings, ui, data };
Any change re-triggers subscribers.
Fix
Split slices by domain, not screen.
authSlice
userSlice
settingsSlice
5. Referential Equality Issues
Problem
return { ...state };
Even unchanged data creates a new reference.
Fix
- Update only changed fields
- Let Immer handle immutability
- Avoid unnecessary spreading
6. Incorrect useCallback / memo Usage
Anti-Pattern
useCallback(() => doSomething(), []);
Used blindly without profiling.
Rule
- Memoization is a tool, not default behavior
- Measure before optimizing
7. Redux DevTools Profiling
What to Look For
- High-frequency actions
- Repeated identical updates
- Large state diffs
- Expensive reducers
Use:
- Action replay
- Time-travel
- Diff view
8. Normalization Problems
Bad
posts: [{ comments: [{ replies: [] }] }];
Good
posts: {
byId, allIds;
}
comments: {
byId, allIds;
}
Normalization reduces update scope.
9. Middleware Overhead
Too many middlewares:
- Logging
- Analytics
- Custom interceptors
Disable heavy middleware in production.
10. When Redux Is NOT the Problem
Often performance issues are due to:
- Expensive React components
- Heavy DOM trees
- Poor memoization
- Layout thrashing
Redux gets blamed unfairly.
Senior Interview Takeaway
Redux performance issues are rarely about Redux itself — they are about how you model state and subscribe to it.
Final Verdict Summary
| Topic | Recommendation |
|---|---|
| Server state | Prefer React Query unless Redux is required |
| Global client state | Redux Toolkit |
| Performance | Optimize selectors, not reducers |
| Interviews | Explain trade-offs, not tools |