skip to content

What concrete signals tell you that a piece of client state has outgrown React Context and belongs in an external store, and which signals mean you should stay with Context?

level: seniorimportance: must knowfreq 55%

answer

  1. frequency times consumer breadth
  2. who reads it outside the tree
  3. does it outlive the provider
  4. consumed whole means stay
  5. re-render pain is not always architecture

basics

~20 s

Move to a store when many consumers each need a different slice of frequently-changing state, when non-React code reads or writes it, or when you need middleware, devtools and persistence. Stay with Context for rarely-changing values consumed whole.

solid answer

~50 s

I look for four signals. First, update frequency crossed with consumer count: if a value changes many times a second and dozens of components each care about one field, Context's all-or-nothing propagation is the wrong shape and a selector subscription is right. Second, consumers outside React — an interceptor, a socket handler, a background module — which cannot read a context value at all. Third, lifetime: state that must exist before mount, persist across routes, or rehydrate from storage wants an owner independent of the tree. Fourth, tooling needs — an action timeline, middleware, or reproducible bug reports. I stay with Context for theme, locale, flags, the current user and service handles: consumed whole, changed rarely, no outside readers. The failure I push back on is adopting a store to fix re-renders that restructuring or memoization would fix; that is a rendering problem wearing an architecture costume.

go deeper

for a junior

Recall the default: keep state as local as possible, use Context to avoid threading props for values like theme or the current user, and do not add a store just because an app is growing.

for a middle

Explain the two properties a store adds that Context lacks — per-consumer selector subscription and existence independent of the tree — and give a concrete example that needs each.

for a senior

An interviewer expects a decision procedure with evidence: update frequency, consumer breadth, outside readers, required lifetime, plus the honest costs of adopting a store and the re-render fixes that are cheaper.

for a principal

Own the tradeoff at codebase scale: a store is a convention every team must follow, so weigh its debuggability and consistency benefits against colocation loss, and define which categories of state are allowed in it.

## Frame the question correctly first "Context or a store" is only a real question for *client* state — state your UI invents and owns. Cached server data is a separate problem with its own answer, and a large share of teams who think they need a store actually need request caching. Set that aside, then ask the remaining question about the state that is genuinely yours. ## Signals that the state has outgrown Context **Update frequency multiplied by consumer breadth.** Context publishes one value to every reader. That is fine at one update per session and painful at sixty per second across forty consumers, because every consumer wakes for every change regardless of which field moved. When the natural sentence describing your state is "most components need a small slice of it and it changes constantly", you are describing what selector-based subscription exists for. **Readers or writers outside the component tree.** A context value can only be read by a component rendering under the provider. If an HTTP interceptor, an event handler registered with a browser API, or a plain module must read or update the value, Context cannot express that at all — this is a capability gap, not a performance tradeoff, and it settles the question on its own. **Lifetime that does not match the tree.** State that must be initialised before the first render, survive a route change that unmounts the provider, or rehydrate from storage on boot wants an owner that is not a component. You can bolt this onto a provider, but you are re-implementing a store badly. **Multiple independent writers that must not clobber each other.** Once several unrelated features update the same state, you want updates expressed as named, serialisable actions with one place that applies them — which is also what makes the change history readable. **Debuggability demands.** An action timeline, middleware for logging or analytics, and the ability to reconstruct a user's state from a bug report are real product requirements in some apps. Context gives you none of them; a store's ecosystem is largely about this. **Cross-cutting derived data.** When one screen needs a value computed from several independent slices, and recomputing it in each consumer is wasteful or inconsistent, a store with memoized derivations is the better home. ## Signals to stay exactly where you are - The value is **consumed whole**: a theme object, a locale, a feature-flag set. There is no slice to select. - It **changes rarely** — on login, on a settings save, on a deliberate toggle. Re-rendering the subtree then is not a problem worth architecture. - It is a **handle, not data**: an API client, a logger, an analytics instance. Context is the natural injection point and the value never changes. - It is **naturally scoped to a subtree**, and different branches should legitimately see different values. Nested providers express that directly; a global store has to invent scoping. - The app is small and the team is small. Every abstraction you add is one a newcomer must learn. ## What a store honestly costs Be able to argue the other side. A store adds a second state system, so every future change begins with "where does this live?". It adds indirection between an interaction and the state change. It adds bundle weight and a dependency you must keep current. It tempts teams to make everything global, which destroys colocation and makes components unusable in isolation. And it does not make a slow component fast — a store cures broad *subscription*, not expensive renders. ## The trap worth naming in the interview The most common bad migration starts with a profiler trace showing too many re-renders. Adopting a store is one fix, but often the cheaper ones are structural: move state down to where it is used, restructure so the expensive subtree is passed as children rather than re-rendered, or let build-time auto-memoization handle the identity churn. If the state does not need to be global and no outside code reads it, a store is a large answer to a small question. ## How I would actually decide Write the sentence: "*this* state is read by *these* consumers, changes *this* often, and must live *this* long." If the consumers include non-components, or the frequency-times-breadth product is high with slice-level interest, or the lifetime exceeds the provider's, take the store. Otherwise keep Context, and revisit when one of those three facts changes rather than on principle.

  • A profiler trace shows a context update re-rendering thirty components. Is that on its own a reason to adopt a store?
    No. First ask whether those components need the state at all — often it can move down closer to its users, or the expensive subtree can be passed through as children so it is not re-created. Only when many consumers genuinely need different slices of the same frequently-changing value does selector subscription become the real fix.
  • How would you keep the migration reversible if you do move a context to a store?
    Keep the custom hook that components already call as the seam. Consumers import useCart, not the context or the store, so the implementation swaps underneath without touching call sites. Migrate one state slice at a time and delete the context only once nothing reads it, so you are never maintaining two owners of the same value.
  • Does adopting a store remove the need for Context entirely?
    No, and expecting that is a smell. Context still injects things that are per-tree rather than global: the store instance itself, a theme, a locale, a scoped configuration for a subtree, and test doubles. A store owns state; Context supplies dependencies. Apps that delete Context usually end up with module singletons that are harder to test.

saying these in an interview costs you the question

  • Any app past a certain size needs Redux
  • Context does not scale, so avoid it
  • A store is always faster than Context
  • Global state is easier to reason about
  • Move it to a store whenever you see re-renders

context