ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

Context API vs Redux

Short answer

Context is not a state manager — it is a way to pass a value down the tree without prop drilling. Redux is a state container with a defined update mechanism, middleware and devtools. Context solves delivery; Redux solves ownership, transitions and debugging. Comparing them directly is the reason people get this wrong.

Context APIRedux Toolkit
What it isA delivery mechanismA state container
Avoids prop drillingYesYes
Holds state itselfNo — you pair it with useStateYes
Re-render controlNone — all consumers re-renderSelectors, only what changed
DevtoolsReact DevTools onlyTime-travel, action log, diffs
MiddlewareNoYes — logging, persistence, async
Bundle costZero — it is in React~12 KB gzipped with RTK
Best forRarely changing app-wide valuesComplex, frequently changing state
They overlap on one job and differ on everything else.

Context is not a state manager

The whole confusion starts here. Context has no state of its own. It takes a value you already have and makes it available to any descendant without passing it through every layer between. The state still has to live somewhere — normally a useState or useReducer in the provider component — and context only carries it.

The state is in useState; context only delivers itjsx
1const ThemeContext = createContext(null);
2
3function ThemeProvider({ children }) {
4  const [theme, setTheme] = useState("dark");   // ← the actual state
5  return (
6    <ThemeContext.Provider value={{ theme, setTheme }}>
7      {children}
8    </ThemeContext.Provider>
9  );
10}
11
12function Button() {
13  const { theme } = useContext(ThemeContext);   // ← no props were drilled
14  return <button className={theme}>Buy</button>;
15}
16// → the comparison is really "useState + context" vs Redux

Once you frame it that way, the real question stops being "which library" and becomes "does my state need what Redux adds" — a defined update mechanism, selector-based subscriptions, middleware and a devtools timeline. For a theme string, plainly not. For a collaborative editor's document model, plainly yes.

The re-render problem

This is the concrete, demonstrable reason context struggles as a store, and the part most explanations skip. When a context value changes, every component calling useContext for it re-renders — all of them, regardless of which part of the value they read. Context has no selector mechanism, and React.memo does not help because the update arrives through context rather than through props.

One field changes, everything re-rendersjsx
1const AppContext = createContext(null);
2
3function Provider({ children }) {
4  const [user, setUser] = useState(null);
5  const [cart, setCart] = useState([]);
6  const [theme, setTheme] = useState("dark");
7
8  return (
9    <AppContext.Provider value={{ user, setUser, cart, setCart, theme, setTheme }}>
10      {children}
11    </AppContext.Provider>
12  );
13}
14
15function ThemeToggle() {
16  const { theme, setTheme } = useContext(AppContext);
17  // → re-renders when the CART changes. It does not read the cart.
18}
19// Adding one item to the cart re-renders every consumer in the app.

There is a second, subtler version that catches people who have done everything else right. The object literal passed as `value` is recreated on every render of the provider, so even when none of the underlying state changed, a re-render of the provider's parent gives every consumer a brand-new value and re-renders them all.

The value identity trapjsx
1// New object every render → every consumer re-renders every time
2<AppContext.Provider value={{ user, setUser }}>
3
4// Stable identity → consumers re-render only when user actually changes
5const value = useMemo(() => ({ user, setUser }), [user]);
6<AppContext.Provider value={value}>
7
8// Even better: split by change frequency
9<UserContext.Provider value={user}>
10  <SetUserContext.Provider value={setUser}>
11// → setUser never changes, so components that only dispatch never re-render

Making context perform: the split-context pattern

You can get a long way with context by separating the value from the way you change it. Most components either read state or dispatch changes, rarely both. Two contexts — one for the data, one for the dispatch function — means the dispatch-only components never re-render, because a dispatch function's identity is stable for the life of the component.

useReducer plus two contexts, which covers most appsjsx
1const StateContext = createContext(null);
2const DispatchContext = createContext(null);
3
4function CartProvider({ children }) {
5  const [state, dispatch] = useReducer(cartReducer, initial);
6  // dispatch is referentially stable — React guarantees it
7  return (
8    <StateContext.Provider value={state}>
9      <DispatchContext.Provider value={dispatch}>
10        {children}
11      </DispatchContext.Provider>
12    </StateContext.Provider>
13  );
14}
15
16function AddButton({ sku }) {
17  const dispatch = useContext(DispatchContext);
18  return <button onClick={() => dispatch({ type: "add", sku })}>Add</button>;
19  // → never re-renders when the cart changes. It only writes.
20}

That pattern — useReducer for the transitions, split contexts for delivery — is genuinely close to Redux in shape, with no dependency and no boilerplate beyond the reducer. If you can describe it in an interview you have effectively answered "could you build a small Redux?", which is a question that follows this one surprisingly often.

This is 1 of 75+ questions in the React Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the React Interview Kit → ₹399

What Redux adds that context cannot

Three things, and being able to name them precisely is the difference between an opinion and an argument.

First, selective subscriptions. `useSelector` runs your selector after every dispatch and re-renders the component only if the selected slice actually changed, so a component reading `state.cart.total` ignores every user or theme update. Context has no equivalent, and this is the gap that matters most as an app grows.

Selectors are the feature context lacksjsx
1function CartBadge() {
2  const count = useSelector(s => s.cart.lines.length);
3  // → re-renders only when the number of lines changes.
4  //   Theme changes, user changes and price edits are all ignored.
5  return <span>{count}</span>;
6}
7
8// The context equivalent re-renders on every context change,
9// then React bails out only if the rendered OUTPUT is identical —
10// which still cost you the render.

Second, middleware. Every action passes through a pipeline you control, which is how logging, analytics, persistence to localStorage, crash reporting and optimistic sync get implemented once rather than in fifty components. There is no interception point in context at all.

Third, the devtools. Every action is named, every state change is diffed, and you can step backwards through the sequence that produced a bug. On a complex flow — a checkout that computed the wrong total, a wizard that skipped a step — this turns a two-hour investigation into a two-minute one. It is the single most underrated reason to keep Redux.

So when do you actually need Redux?

There is a usable test. If your state changes frequently, is read by many unrelated components, and moves through transitions complex enough to deserve names, a store earns its place. If any one of those is false, context or plain state is probably enough — and if all three are false, you are adding a dependency to avoid passing two props.

StateChangesReadersUse
Theme, localeRarelyManyContext
Logged-in userRarelyManyContext
Form draftOftenOne subtreeuseState, lifted
Shopping cartOftenManyStore, or reducer + split context
Editor undo stackOftenManyRedux — devtools pay for themselves
Fetched API dataOftenManyReact Query, not either
Modal open/closedOftenOne or twouseState
A decision table you can reason from out loud.

The lighter alternatives

The choice is no longer binary. Several small libraries give you selective subscriptions — the feature context lacks — without Redux's ceremony, and they are a reasonable default for new projects that need a store but not a full architecture.

OptionSizeStrengthWeakness
Context + useReducer0 KBNo dependency, familiarNo selectors
Zustand~1 KBSelectors, no providerThinner devtools
Jotai~3 KBAtomic, fine-grainedDifferent mental model
Redux Toolkit~12 KBDevtools, middleware, ecosystemMost ceremony
React Query~13 KBServer state done properlyNot for client state
What each option is actually good at.
Zustand, for scale — this is the whole storejavascript
1const useCart = create((set) => ({
2  lines: [],
3  add: (line) => set((s) => ({ lines: [...s.lines, line] })),
4  clear: () => set({ lines: [] }),
5}));
6
7function Badge() {
8  const count = useCart((s) => s.lines.length);
9  // → selector-based, so this ignores unrelated changes.
10  //   No provider, no slice, no dispatch.
11  return <span>{count}</span>;
12}

Context in Next.js and Server Components

One practical wrinkle worth knowing, because it comes up in every Next.js App Router project. Context is a client-side feature: a provider needs `"use client"`, and Server Components cannot consume context at all. Wrapping your whole app in a provider therefore pulls the entire tree into the client bundle, which defeats the point of Server Components.

Keep the provider as low as it will gojsx
1// app/providers.tsx
2"use client";
3export function Providers({ children }) {
4  return <ThemeProvider>{children}</ThemeProvider>;
5}
6
7// app/layout.tsx — a Server Component
8export default function Layout({ children }) {
9  return <body><Providers>{children}</Providers></body>;
10}
11// → children passed through stay Server Components; only components
12//   that actually call useContext become client components.

The rule that follows: pass Server Components as `children` through a client provider rather than importing them inside it. The provider renders on the client, the children were already rendered on the server, and you keep both.

Is context slow?

Context itself is fast — the propagation cost is negligible. What is slow is re-rendering every consumer when only one of them cared about the change. Split contexts by change frequency, memoise the value, and the problem largely disappears for values that change occasionally.

Can I use multiple contexts?

Yes, and you should. Multiple narrow contexts perform better than one wide one because each consumer subscribes only to what it needs. The usual split is one per concern — theme, auth, locale — plus the state/dispatch split within a concern that changes often.

Does React.memo prevent context re-renders?

No. React.memo compares props, and a context update does not arrive through props. A memoised component that calls useContext still re-renders when that context's value changes. The only ways out are narrower contexts or a store with selectors.

What is useContextSelector?

A proposed API that would let a component subscribe to part of a context value, giving context the selector behaviour it currently lacks. It is not in React yet; the use-context-selector package implements it today, and most teams reach for a small store instead.

Do I need Redux for a medium-sized app?

Usually not, if server data is handled by a query library. What is left is generally a handful of client concerns that context or a small store covers. Add Redux when you want the devtools and middleware, or when the state transitions are complex enough that named actions genuinely help.

Frequently asked questions

What is the difference between Context API and Redux?
Context is a mechanism for passing a value down the component tree without prop drilling — it holds no state itself. Redux is a state container with a defined update mechanism, selector-based subscriptions, middleware and devtools. Context solves delivery; Redux solves managing and debugging state.
Can Context API replace Redux?
For app-wide values that change rarely — theme, locale, the current user — yes, comfortably. For frequently changing state read by many components it struggles, because every consumer re-renders on every change and there is no way to subscribe to part of the value.
Why use Redux instead of Context API?
Three reasons that context cannot match: selectors, so a component re-renders only when the slice it reads changes; middleware, for logging, persistence and analytics in one place; and time-travel devtools, which turn a hard state bug into a short investigation.
Is Context API bad for performance?
Not inherently. The problem is that a context update re-renders every consumer regardless of which part of the value they use. Memoise the provider value with useMemo, split large contexts into smaller ones, and separate state from dispatch — that covers most of the cost.
Why do all my components re-render when I use context?
Either the value changed and every consumer is subscribed to the whole thing, or — more often — you are passing a fresh object literal as value, so its identity changes on every provider render even when the underlying state did not. Wrap it in useMemo.
Does React.memo stop context re-renders?
No. React.memo only compares props, and context updates do not arrive as props. A memoised component that calls useContext will still re-render whenever that context's value changes. Narrower contexts or a store with selectors are the real fixes.
When should I use useReducer with Context?
When several related values change together and the transitions are worth naming — a cart, a multi-step form, a filter panel. Put the reducer's state in one context and its dispatch in another, so components that only dispatch never re-render.
Is Redux still needed in 2026?
Less often than it used to be. Server data belongs in a query library, prop drilling is handled by context, and lighter stores cover most client state. Redux still wins where you want middleware and time-travel debugging, or where a large team benefits from explicit named actions.
What is the difference between Context API and Zustand?
Zustand is an actual store with selector-based subscriptions, so components re-render only when the part they read changes — the feature context lacks. It also needs no provider. Context is built into React with zero bundle cost; Zustand is about 1KB and better suited to frequently changing state.
Can Server Components use Context?
No. Context is a client-only feature, so a provider must be marked "use client" and Server Components cannot call useContext. Keep providers as low in the tree as possible and pass Server Components through as children so they are not pulled into the client bundle.
How many contexts is too many?
There is no hard limit, and more narrow contexts usually perform better than fewer wide ones. The practical ceiling is readability: once the provider nesting is hard to follow, wrap them in a single Providers component, or move the frequently changing pieces into a store.
Should I use Context or Redux for authentication state?
Context, in most cases. The current user changes rarely — at login, logout and token refresh — which is exactly the profile context handles well. The token itself should live in an HttpOnly cookie rather than in either, with only the user's display data in the context.

This is 1 of 75+ questions in the React Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the React Interview Kit → ₹399
Written by Arun Karthikeyan · Last updated

Full kit

React Interview Kit · ₹399

Get it →