Advanced6 questionsFull Guide

State Management Architecture — System Design Interview Guide

Master server state vs client state, when to use React Query / SWR vs Redux / Zustand, global vs local state patterns, and how to design scalable state architecture for complex applications — the complete system design answer for state management.

The Mental Model

The most important insight in modern frontend state management is that there are two fundamentally different types of state: server state (data that lives on the server and you're synchronizing with) and client state (UI state that exists only on the frontend). Mixing them in the same store is the root cause of most state management complexity. Once you separate them, the right tool for each becomes obvious.

The Explanation

The Two Types of State

Server State

Data that originates on the server and your app displays. It's always potentially stale — another user might have changed it. It needs loading/error states, background refetching, caching, and pagination.

  • User list from an API
  • Product inventory
  • Dashboard metrics

Best managed by: React Query, SWR, RTK Query

Client State

State that exists only in your frontend — the server doesn't know or care about it.

  • Modal open/closed
  • Currently selected tab
  • Form input in progress
  • Filter sidebar expanded

Best managed by: useState, useReducer, Zustand, or Context

React Query — Server State Done Right

React Query handles all the complexity of server state: caching, background refetching, deduplication, pagination, optimistic updates. You declare what you want; React Query handles when and how to fetch it.

function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
    staleTime: 60_000,     // treat as fresh for 1 min
    refetchOnWindowFocus: true, // refetch when tab regains focus
  });

  // Mutations with optimistic updates
  const mutation = useMutation({
    mutationFn: updateUser,
    onMutate: async (newUser) => {
      await queryClient.cancelQueries(['user', userId]);
      const previous = queryClient.getQueryData(['user', userId]);
      queryClient.setQueryData(['user', userId], newUser); // optimistic
      return { previous };
    },
    onError: (err, newUser, context) => {
      queryClient.setQueryData(['user', userId], context.previous); // rollback
    },
  });
}

When Does Redux Actually Make Sense?

Redux is often added out of habit. Ask before reaching for it:

  • Is this server state? → Use React Query/SWR instead
  • Is this local to one component? → Use useState
  • Is this shared across many components but client-only? → Use Zustand or Context

Redux genuinely shines for: complex client-side state with many actors and time-travel debugging needs (undo/redo, replaying actions), or large teams needing strict action-based patterns for auditability.

Zustand — Minimal Global State

When you need shared client state without Redux's boilerplate. A Zustand store is a hook.

import { create } from 'zustand';

const useUIStore = create((set) => ({
  sidebarOpen: false,
  theme: 'light',
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
  setTheme: (theme) => set({ theme }),
}));

// In any component — no Provider required
const { sidebarOpen, toggleSidebar } = useUIStore();

The Decision Framework

Is it from a server/API?
  Yes → React Query / SWR / RTK Query
  No →
    Is it needed by more than one component tree?
      No → useState / useReducer
      Yes →
        Is it simple? → Context (or Zustand)
        Is it complex with many mutations? → Zustand (or Redux if team prefers)

Common Misconceptions

⚠️

Redux is required for large React apps — many large production apps (including parts of Facebook) use React Query + local state with no global store. Redux is one option, not a requirement.

⚠️

Context API is a state management solution — Context is a dependency injection mechanism, not a state manager. It doesn't handle caching, async, or performance optimization. Use it for stable, rarely-changing values.

⚠️

You should store API responses in Redux — this is the root cause of 'why is Redux so complicated'. Server state (API data) should live in React Query, which handles caching and synchronization natively.

⚠️

More state in global store = better — global state is harder to reason about, test, and colocate. Keep state as local as possible and only hoist when genuinely shared.

Where You'll See This in Real Code

GitHub.com: uses React Query for PR/issue data, local state for UI (selected files in diff), and minimal global state for user session

Notion: document state is server-synced with real-time updates (similar to React Query mutations) + local state for selection, cursor position, panel open/closed

Linear: uses Zustand-like patterns for UI state + a custom sync layer for server state — the issue list is always synced from the server, not stored in Redux

Interview Cheat Sheet

  • Server state: data from API, always potentially stale → React Query / SWR / RTK Query
  • Client state: UI-only state, frontend owns it → useState, Zustand, Context
  • React Query: queryKey for cache identity, staleTime for freshness, mutations for updates
  • Zustand: minimal boilerplate global store; no Provider; select only what you use (avoid re-renders)
  • Context: fine for stable values (theme, locale, user); bad for frequently changing values (causes re-renders)
  • Redux: justified for complex client state with undo/redo, devtools, or strict audit requirements
  • colocation principle: keep state as close to where it's used as possible
💡

How to Answer in an Interview

  • 1.Open with the server state vs client state distinction — most candidates skip it and immediately say 'Redux'. This insight alone elevates your answer.
  • 2.The 'when would you NOT use Redux' follow-up is common — answer: most server state should be in React Query; most simple UI state should be local useState
  • 3.Optimistic updates with rollback is a strong signal of senior-level experience — describe the pattern: update cache immediately, rollback on error
  • 4.React Query's staleTime and gcTime are commonly confused — staleTime is when data becomes 'stale' (still used, but triggers background refetch); gcTime is when unmounted query data is garbage collected

Practice Questions

6 questions
#01

What is the difference between server state and client state?

EasyState Management PRO💡 Server state lives on the server and must be fetched/synchronized; client state is local to the browser and doesn't need network sync
#02

When would you choose Zustand over Redux Toolkit?

EasyState Management PRO💡 Zustand is minimal and boilerplate-free; Redux Toolkit shines for large teams needing strict patterns, devtools, and middleware
#03

What is React Query's staleTime vs gcTime (cacheTime) and how do they interact?

EasyState Management PRO💡 staleTime = how long data is considered fresh; gcTime = how long inactive cache entries are kept before garbage collection
#04

What is optimistic updating and how do you implement it with React Query?

EasyState Management PRO💡 Update the UI immediately before the server confirms — rollback on error; use onMutate to snapshot, onError to restore, onSettled to invalidate
#05

When should you use URL state instead of React state?

EasyState Management PRO💡 If the state should survive a page refresh, be shareable via a link, or be bookmarkable — put it in the URL
#06

What are the performance pitfalls of React Context and how do you avoid them?

MediumState Management PRO💡 Every consumer re-renders when any value in context changes — split contexts by update frequency, memoize values, or use Zustand instead

Related Topics

Authentication Architecture (JWT, Cookies, SSO) — System Design Interview Guide
Senior·10–15 Qs
Caching Strategies — System Design Interview Guide
Advanced·8–10 Qs
Rendering Strategies (SSR, CSR, SSG, ISR) — System Design Interview Guide
Senior·10–15 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz