Back to React
React
easy
mid

What are the trade offs between Redux, Zustand, and Context for state management?

Context: built-in, no extra deps, but every consumer re-renders on any value change — best for low-frequency cross-tree config (theme, auth user). Redux/RTK: predictable + devtools + middleware ecosystem, opinionated boilerplate, best for complex client domains. Zustand: minimal API, selector-based subscriptions, ~1kb, best default for small/medium apps. Server state? None of these — use React Query.

5 min read·~12 min to think through

Picking a state manager depends on what kind of state and how often it changes.

Categories of state first

KindExamplesRight tool
Server stateLists, single records, mutationsReact Query / SWR (NOT Redux/Zustand)
Local UI stateinput value, modal openuseState
Form statemulti-field formsReact Hook Form + Zod
Cross-tree configtheme, auth user, feature flagsContext
Cross-tree app statecart, editor doc, design stateZustand or Redux
Routing stateURL params, searchrouter

The #1 mistake: putting server state in Redux/Zustand. It's stale by default; you reinvent caching + revalidation badly.

Context

tsx
const ThemeCtx = createContext("light");
function App() { return <ThemeCtx.Provider value={theme}><Tree /></ThemeCtx.Provider>; }

Strengths: built-in, simplest possible. Weakness: any change to the value re-renders every consumer regardless of which field they read. Fine for theme/auth (changes rarely). Bad for "current selection" in an editor that changes per keystroke.

Redux / RTK

Strengths:

  • Single source of truth, time-travel devtools, predictable updates via reducers.
  • Middleware ecosystem (logging, thunks, sagas).
  • useSelector only re-renders when the selected slice changes.
  • RTK collapses boilerplate (createSlice); RTK Query adds React-Query-like data fetching.

Weaknesses:

  • More ceremony than Zustand.
  • Heavier bundle (~6kb gz core, more with toolkit).
  • Reducer indirection can feel like overkill for simple stores.

When: large complex client domains (Linear-style editor, design tools), teams already familiar, devtools are critical.

Zustand

tsx
const useCart = create((set) => ({
  items: [],
  add: (i) => set((s) => ({ items: [...s.items, i] })),
}));

function Count() { return <span>{useCart((s) => s.items.length)}</span>; }

Strengths:

  • Tiny (~1kb), no provider needed.
  • Selector subscriptions out of the box (only re-renders when selected slice changes).
  • Mutate-style API via Immer middleware if desired.
  • Easy to test (stores are plain hooks).

Weaknesses:

  • Less opinionated structure → teams must agree on patterns (slices, persist, devtools middleware).
  • Less mature ecosystem than Redux.

When: most new apps. Sane default.

What about Jotai / Recoil / Valtio?

  • Jotai: atoms-as-primary, derived state graph. Great for editor-like apps with many fine-grained pieces.
  • Recoil: similar; Facebook-stalled.
  • Valtio: proxy-based "mutate freely" model.

Niches; consider when the data graph is genuinely atomic.

Decision tree

ts
Is this data from the server?
  → Yes: React Query / SWR / RTK Query.
No:
    Cross-tree?
      → No: useState locally.
Yes:
        Changes rarely (theme, auth)?
          → Context.
        Changes often or large state?
Zustand (default) or Redux (if devtools/middleware critical).

Interview framing

"First I separate server state from client state — server state belongs in React Query, not in Redux/Zustand. For local UI state, useState. For cross-tree config that changes rarely (theme, auth, feature flags), Context. For meaningful client state — cart, editor doc, design tools — Zustand as the default because it's small, has selector subscriptions, and no provider boilerplate. Redux/RTK for large complex domains where time-travel devtools and middleware ecosystem earn their cost. The big anti-pattern is putting server state in a client store and reinventing caching/revalidation."

Follow-up questions

  • Why is Context bad for high-frequency updates?
  • When does Redux's devtools earn its weight?
  • How does Zustand's selector subscription work under the hood?

Common mistakes

  • Server state in Redux/Zustand.
  • Mega-Context that re-renders the whole app on every change.
  • Using Redux for tiny apps with no cross-tree state.

Performance considerations

  • Selector-based subscriptions (Redux useSelector, Zustand selector) prevent fan-out re-renders. Context lacks this and is hot-path-hostile.

Edge cases

  • SSR with Zustand — store created per request to avoid cross-user leak.
  • Context value is a new object every render → tears everything; memoize.
  • Persist middleware (localStorage) + SSR — hydration mismatch.

Real-world examples

  • Zustand: Excalidraw, Linear's web. Redux: legacy Slack, Trello. Jotai: editor-heavy apps. React Query for nearly every server-data app.

Senior engineer discussion

Seniors lead with the server-state-vs-client-state distinction, justify Zustand-as-default for new apps, and reach for Redux only when its specific superpowers (devtools, middleware) are required.

Related questions