What is the difference between client state and server state?
Assesses fundamental understanding of React conventions, runtime behavior, and memory/performance considerations.
Hiring managers look for precision, avoidance of ambiguous jargon, and ability to explain trade-offs under real production conditions.
Client state is data that lives entirely in the browser and is owned by the UI — it doesn't need to be synchronized with a backend. Server state is data that actually lives on a remote server/database; the client only holds a cached copy of it, which can go stale at any time.
| Aspect | Client state | Server state |
| --- | --- | --- |
| Ownership | Owned entirely by the UI | Owned by the backend/database; UI only has a cached copy |
| Examples | Form input values, modal open/closed, selected tab, theme, filters before submit | Fetched user profile, product list, comments, any REST/GraphQL response |
| Persistence | Usually reset on refresh (unless explicitly persisted) | Persists on the server regardless of client refreshes |
| Staleness | Never "stale" — it's the source of truth itself | Can become stale/out of date; needs refetching, caching, and invalidation |
| Sharing | Typically local to one component/feature | Often shared across many components/screens |
| Async concerns | None — synchronous updates via setState/reducers | Requires handling loading, error, retries, pagination, race conditions |
| Typical tools | useState, useReducer, Context, Redux/Zustand | React Query / TanStack Query, RTK Query, SWR, Apollo Client |
#### Why the distinction matters
Treating server state like client state (e.g., dumping a fetched API response into useState or a Redux slice and manually managing loading/error flags) reinvents caching, deduplication, background refetching, and invalidation by hand. Dedicated server-state libraries solve these problems out of the box:
// Server state handled manually — lots of boilerplate to get right
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then(setUsers)
.catch(setError)
.finally(() => setLoading(false));
}, []);
// ...
}
// Server state via React Query — caching, refetching, and status handled for you
function Users() {
const { data: users, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: () => fetch("/api/users").then((res) => res.json()),
});
// ...
}
In short, use local state/Context/Redux for client state (UI-only concerns), and a data-fetching library like React Query, RTK Query, or SWR for server state (anything that mirrors data owned by a backend).
Candidate Response Strategy & Interview Tips
- Start with a concise one-sentence summary: Deliver a direct, confident answer first before expanding into nuances.
- Demonstrate real-world trade-offs: Discuss where this approach excels and when you would avoid it in production systems.
- Discuss complexity & edge cases: Proactively explain time/space complexity or boundary conditions (null values, scale limits).
- Prepare for interviewer follow-ups: Technical hiring panels frequently probe deeper into concurrency, backward compatibility, or alternative libraries.