How do you optimize React context performance?

React

The 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 everything
const AppContext = createContext();
// Good — separate contexts
const 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 changes
function Display() {
const state = useContext(StateContext);
return <p>{state.count}</p>;
}
// This NEVER re-renders on state changes
function 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 Header
function 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 useMemo to 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.