How do you optimize React context performance?
ReactThe short answer
The main strategies are: split large contexts into smaller ones, memoize the context value object, separate state and dispatch into different contexts, and use React.memo on consumer components. For frequently changing data, consider using a state management library with selective subscriptions instead of context.
Strategy 1: Split contexts
Instead of one big context, split it by how often each piece of data changes:
// Bad — one context for everythingconst AppContext = createContext();// Good — separate contextsconst AuthContext = createContext();const ThemeContext = createContext();const SettingsContext = createContext();When the theme changes, only components consuming ThemeContext re-render. Components consuming AuthContext are not affected.
Strategy 2: Memoize the context value
function ThemeProvider({ children }) { const [theme, setTheme] = useState('light'); // Memoize so consumers don't re-render on parent re-renders const value = useMemo( () => ({ theme, setTheme }), [theme] ); return ( <ThemeContext.Provider value={value}> {children} </ThemeContext.Provider> );}Without useMemo, every render of ThemeProvider creates a new object, causing all consumers to re-render even if theme has not changed.
Strategy 3: Separate state and dispatch
A common pattern is to split the context into two — one for the state (changes often) and one for the dispatch function (never changes):
const StateContext = createContext();const DispatchContext = createContext();function AppProvider({ children }) { const [state, dispatch] = useReducer( reducer, initialState ); return ( <DispatchContext.Provider value={dispatch}> <StateContext.Provider value={state}> {children} </StateContext.Provider> </DispatchContext.Provider> );}Components that only dispatch actions (like buttons) use DispatchContext and never re-render when state changes. Components that read state use StateContext.
// This only re-renders when state changesfunction Display() { const state = useContext(StateContext); return <p>{state.count}</p>;}// This NEVER re-renders on state changesfunction Controls() { const dispatch = useContext(DispatchContext); return ( <button onClick={() => dispatch({ type: 'increment' })}> + </button> );}Strategy 4: Memo on consumers
Wrap consumer components in React.memo and make sure the context only passes stable references:
const UserDisplay = React.memo(function UserDisplay({ name,}) { return <p>{name}</p>;});function UserSection() { const { user } = useContext(UserContext); return <UserDisplay name={user.name} />;}UserDisplay only re-renders when name actually changes, even if the parent re-renders because of context changes.
Strategy 5: Composition instead of context
Sometimes you do not need context at all. Component composition can solve the prop drilling problem:
// Instead of using context to pass user to deeply nested Headerfunction App() { const user = useUser(); return <Layout header={<Header user={user} />} />;}function Layout({ header }) { return ( <div> {header} <Sidebar /> <Main /> </div> );}Layout does not need to know about user. It just renders whatever header it receives. No context needed.
When to stop using context
If you find yourself:
- Splitting contexts into many pieces just to avoid re-renders
- Adding
useMemoto every provider - Wrapping every consumer in
React.memo
It might be time to use a state management library like Zustand, Jotai, or Redux Toolkit. These libraries have built-in selective subscriptions — components only re-render when the specific piece of state they use changes.
Interview Tip
Start with the simplest optimization (split contexts), then work up to the more advanced ones (separate state/dispatch, composition). The key insight interviewers want to hear is that context causes all consumers to re-render, and the strategies above reduce the blast radius of each change. If you mention the composition pattern as an alternative to context, that shows advanced React thinking.
Why interviewers ask this
This is an advanced React question that tests real-world experience. Interviewers want to see if you have hit performance problems with context and know the optimization strategies. A candidate who can explain the state/dispatch split and knows when to switch to a state management library shows they have built and optimized complex React applications.