State Management Architecture: Client vs Server State
Modern frontend state architecture separates Client State (ephemeral UI state like modals and forms) from Server State (asynchronous remote data requiring caching, deduplication, and invalidation).
Why it matters in interviews
Visual & Interactive Explanation
1. Is the state fetched from a remote API?
YES -> Use Server State Library (TanStack Query / RTK Query) with cache keys & staleTime
2. Should the state be shareable / bookmarkable?
YES -> Use URL Search Params (nuqs / useRouter) so users can link directly to active filters
3. Is state used only within one component tree?
YES -> Use useState or useReducer colocated inside the parent component
4. Is it low-frequency global data (auth/theme)?
YES -> Use React Context API with clean Provider wrapper
5. Is it high-frequency shared client state?
YES -> Use Zustand with atomic selectors (prevents unnecessary re-renders)
Code Examples & Implementation
import { useMutation, useQueryClient } from '@tanstack/react-query';
interface Todo {
id: string;
title: string;
completed: boolean;
}
export function useUpdateTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (updatedTodo: Todo) => {
const res = await fetch(`/api/todos/${updatedTodo.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedTodo),
});
if (!res.ok) throw new Error('Update failed');
return res.json();
},
// 1. When mutation starts: snapshot previous cache & optimistically update UI
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] });
const previousTodos = queryClient.getQueryData<Todo[]>(['todos']);
// Optimistically update cache instantly
queryClient.setQueryData<Todo[]>(['todos'], (old) =>
old ? old.map((t) => (t.id === newTodo.id ? newTodo : t)) : []
);
return { previousTodos };
},
// 2. If API fails: rollback to snapshot
onError: (err, newTodo, context) => {
if (context?.previousTodos) {
queryClient.setQueryData(['todos'], context.previousTodos);
}
},
// 3. Always revalidate to ensure client matches database
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
}Interview-Ready Answers
Separate Server State from Client State. Use TanStack Query for remote API data (caching, deduplication, retries), URL search params for bookmarkable filters, and Zustand or useState/Context for local/shared UI state.
Official Documentation & Specifications
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
const ThemeContext = React.createContext("light");
function App() {
const [theme, setTheme] = useState("light");
console.log("App render");
return (
<ThemeContext.Provider value={theme}>
<Sidebar />
<button onClick={() => setTheme("dark")}>Toggle</button>
</ThemeContext.Provider>
);
}
function Sidebar() {
console.log("Sidebar render");
return <Profile />;
}
function Profile() {
const theme = useContext(ThemeContext);
console.log("Profile render:", theme);
return <p>{theme}</p>;
}
// User clicks Toggle. What logs?// Zustand store
const useStore = create((set) => ({
count: 0,
name: "Alice",
increment: () => set((s) => ({ count: s.count + 1 })),
}));
function Counter() {
const count = useStore((s) => s.count);
console.log("Counter render");
return <p>{count}</p>;
}
function Name() {
const name = useStore((s) => s.name);
console.log("Name render");
return <p>{name}</p>;
}
// User calls increment(). What logs?Common Mistakes & Anti-Patterns
- Storing remote API response data in global Redux/Zustand and manually managing loading, error, and stale flags.
- Passing a single monolithic object to Context.Provider without splitting contexts or memoizing values.
- Forgetting to cancel in-flight queries before applying optimistic cache updates.
Real-World Architectural Scenario
A collaborative Kanban board with 20 teams suffers severe typing lag when updating task status. Investigation reveals all 50 board columns subscribe to a single BoardContext. How do you re-architect it?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: