When would you choose Context API vs. a dedicated state management library like Zustand or Redux Toolkit?
Use Context API for low-frequency, shallowly nested, UI-related shared state (e.g., theme, auth status); reach for Zustand or RTK when you need scalable, predictable, debuggable, and performance-optimized global state with middleware, time-travel, or complex async logic.
Short Answer
Choose Context API only for propagation of infrequently updated, cross-cutting concerns (e.g., dark mode toggle, user session) — not for high-frequency or deeply reactive global state. For anything beyond that — especially in medium-to-large apps with shared derived state, async side effects, or strict debugging requirements — adopt Zustand (lightweight, intuitive) or Redux Toolkit (structured, enterprise-ready).
Details
Context API’s main pitfalls are re-renders and bundle bloat via abstraction: every useContext consumer re-renders whenever any part of the context value changes — even if it only cares about one field. This forces manual memoization (useMemo, React.memo) and often leads to “context sprawl” (multiple contexts → maintenance debt). It also lacks built-in devtools, middleware, serialization, or time-travel debugging.
Zustand avoids re-render overhead by letting components subscribe only to the slices they use, uses lightweight proxy-based state updates, and ships at ~1.2 kB gzipped. RTK adds opinionated structure (reducers + thunks/RTK Query), built-in immer, powerful DevTools, and seamless TypeScript inference — all while staying lean (~9 kB gzipped for core + RTK Query).
Bundle size matters: Context is “free” (built-in), but misused Context + excessive memoization can inflate JS size more than adding Zustand. Maintainability suffers when teams build ad-hoc Context wrappers to mimic middleware or persist state — reinventing wheels that Zustand/RTK solve out-of-the-box.
Example
// ✅ Good Context use: rare, atomic, UI-wide config
const ThemeContext = createContext<{ mode: 'light' | 'dark'; toggle: () => void } | null>(null);
// ❌ Bad Context use: frequent updates + multiple subscribers needing subsets
const CartContext = createContext<{ items: Item[]; total: number; addItem: (i: Item) => void } | null>(null);
// → Causes unnecessary re-renders across product listings, headers, modals
// → Better: Zustand store with selective `useStore(state => state.items)`Bonus
To stand out: articulate migration strategy. E.g., “We start with Context for auth/theme, then extract domain-specific stores (cart, search) into Zustand as complexity grows — and we measure re-render counts (via React DevTools profiler) before/after to validate the trade-off.” Bonus points for mentioning useSyncExternalStore (for custom external state) or noting that RTK Query eliminates the need for separate data-fetching libraries — reducing both bundle size and cognitive load.