Explain your strategy for managing global state efficiently in a React app with multiple teams contributing to it.
Separate kinds of state first: server state → React Query; local UI → useState; cross-tree config (theme, auth) → Context; cross-tree app state (cart, editor) → Zustand or Redux Toolkit with selector subscriptions; URL state → router. With multiple teams: shared store package, feature-scoped slices, RFCs for additions, store namespacing to prevent collisions, devtools, and clear ownership.
Step 1 — categorize
| Kind | Tool |
|---|---|
| Server state | React Query / SWR / RTK Query |
| Local UI state | useState |
| Cross-tree config (theme, auth user) | Context |
| Cross-tree app state | Zustand or Redux |
| URL state | router |
| Form state | React Hook Form + Zod |
Putting server state in a client store is the #1 anti-pattern at scale — see [[compare-trade-offs-in-state-management-redux-zustand-context]].
Step 2 — choose by characteristics
- Frequent updates (per keystroke, cursor pos) → Zustand or Redux (selector subscriptions). Avoid Context for these.
- Coordination across many teams → Redux Toolkit's slice pattern + devtools + middleware ecosystem maps to bigger teams better than ad hoc stores.
- Small to medium → Zustand. Lower ceremony, easier to evolve.
Step 3 — multi-team conventions
Multiple teams sharing a store needs governance:
- Feature-scoped slices —
cart,checkout,search, each owned by a team. - Slice file convention —
features/cart/slice.tsexporting state + actions + selectors. - Selectors > raw state access — prevents teams from reading shape they shouldn't.
- Cross-slice rules — slice A can't read slice B's state directly; use exported selectors or events.
- Action namespacing —
cart/itemAdded, notitemAdded. - Migration policy — slices evolve their internal shape behind selectors.
- RFCs for new top-level slices — keeps the store from sprawling.
- Codeowners — feature team owns its slice's PRs.
Step 4 — performance hygiene
- Selector subscriptions — components subscribe to slice they care about (Zustand selector / RTK
useSelector). - Shallow / custom equality — pass
shallowto selector for object slices. - Avoid returning new object references from selectors per call.
- Memoize derived selectors (reselect).
- Split high-frequency UI state out of the global store —
useStatelocally instead.
Step 5 — server state separation
Don't mirror server data in the global store. React Query / SWR / RTK Query own:
- Caching with TTL + revalidation.
- Mutation + invalidation.
- Background refetch on focus.
- Request dedup.
- Optimistic updates.
This eliminates 80% of what people historically put in Redux.
Step 6 — persistence + SSR
- Persist middleware (zustand/persist, redux-persist) for offline / draft data.
- Hydration: avoid mismatches by gating client-only state until mount or by streaming from server.
- SSR: store created per request to prevent cross-user leak.
Step 7 — observability
- Redux DevTools (Zustand has a devtools middleware).
- Action logs in dev.
- Sentry breadcrumbs for actions before errors.
Step 8 — DI for testability
Inject the store / API client. Don't import singletons everywhere. Makes Storybook + tests trivial.
Anti-patterns
- "One big context" → re-renders fan out.
- Server state in Redux/Zustand.
- Slices reading each other's internal shape directly.
- Every team adding a top-level store without governance.
- Global state for what's really local UI (modal open, hover).
Interview framing
"Categorize first: server state → React Query, local UI → useState, cross-tree config → Context, cross-tree app state → Zustand or Redux Toolkit with selector subscriptions, URL state → router. With multiple teams I'd land on RTK with feature-scoped slices (cart/, checkout/), strict selector + action conventions, namespaced action types, codeowners per slice, and RFCs for adding top-level slices. React Query handles server state — never mirror it in the client store. Hygiene: selectors with shallow equality for object slices, memoize derived selectors, avoid context for hot state. Devtools + breadcrumbs + per-request store on SSR."
Follow-up questions
- •Why Zustand vs RTK for multi-team apps?
- •How do you prevent slices from reading each other's shape?
- •Walk through how React Query fits in.
Common mistakes
- •Server state in client store.
- •Context for hot state.
- •Slices coupled to each other.
- •No selector convention.
Performance considerations
- •Selectors with custom equality cut re-renders. Hot UI state stays local, not global.
Edge cases
- •Cross-slice events / dependencies.
- •Persistence + hydration mismatches.
- •SSR store-per-request safety.
Real-world examples
- •Linear's editor store, Figma's selection state, large fintech apps using RTK slices per feature.