Lesson 64 of 80
Context and an external store both solve 'shared state, read in many places' — they just fail at very different scales, and picking wrong is a rewrite waiting to happen.
15 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
"Global state" isn't one problem — it's shorthand for at least two different ones, and Context only solves one of them well.
Context is built for values that change rarely and are read broadly: the current theme, the logged-in user, the active locale. When the value updates, every component subscribed to that context re-renders, no matter which slice of the value they actually read. For something that changes a few times per session, that cost is invisible.
An external store (Redux, Zustand, Jotai — the mechanism differs, the shape doesn't) is built for values that change often and are read selectively: a shopping cart, form state shared across steps, a live collaborative document. It gets there with selectors — each component subscribes to a narrow slice of the store and only re-renders when that specific slice changes, not on every update to the whole store.
// Context: every consumer re-renders on every cart change
const CartContext = createContext(null);
function CartProvider({ children }) {
const [cart, setCart] = useState({ items: [], total: 0 });
return (
<CartContext.Provider value={{ cart, setCart }}>
{children}
</CartContext.Provider>
);
}
// ItemCount only cares about items.length, but Context can't express that —
// it re-renders on every field change, including ones it doesn't read.
function ItemCount() {
const { cart } = useContext(CartContext);
return <span>{cart.items.length}</span>;
}
// External store: a selector subscribes to exactly one slice
const useCartStore = create((set) => ({
items: [],
total: 0,
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
}));
// Only re-renders when items.length actually changes —
// a total update elsewhere in the store doesn't touch this component.
function ItemCount() {
const count = useCartStore((s) => s.items.length);
return <span>{count}</span>;
}
Both examples solve the same problem — many components need the cart — but the store version scales to a cart that updates on every keystroke of a quantity field, where the Context version would re-render the whole subscribed tree on each one.
ThemeContext and CartContext instead of one AppContext means a theme change doesn't re-render cart consumers — this alone fixes a lot of what looks like "Context doesn't scale," without reaching for a new dependency at all.Context exists to solve prop drilling — a value provided once, read anywhere below, without threading it through components that don't care about it. There are real techniques elsewhere in this track for shrinking Context's own re-render cost while staying inside Context — splitting state from dispatch, passing children through to preserve a stable reference, even building a selector on top of useSyncExternalStore for fine-grained subscriptions. This lesson sits one level above all of that: it's the earlier decision of whether to keep optimizing within Context at all, or whether the value has crossed into "changes too often, read too selectively" territory where an external store's selector model is simply the better-fitting tool from the start. It's also worth distinguishing from data fetched from a server and cached client-side — that's state with its own lifecycle entirely, not something either Context or a store is really designed to own.