Intermediate0 questionsFull Guide

React Context API — Complete Interview Guide

Master the React Context API — how to avoid prop drilling, when context re-renders every consumer, the performance optimization pattern, and the honest answer to 'Context vs Redux' that interviewers want to hear.

The Mental Model

React Context is a broadcast channel inside your component tree. Any component that subscribes to a context receives its current value directly — without props being manually threaded down through every intermediate layer. The Provider is the transmitter; useContext is the receiver. Change the broadcasted value and every subscriber re-renders automatically. Context is designed for 'ambient' data — the current user, theme, locale, or feature flags that many components need but shouldn't have to receive as props.

The Explanation

The Problem: Prop Drilling

Prop drilling happens when a piece of data needs to reach a deeply nested component, so you pass it as a prop through every layer in between — even layers that don't use it at all:

// App passes 'user' down through Layout → Sidebar → UserMenu
// Layout and Sidebar don't need user — they just relay it
function App() {
  const user = useAuth();
  return <Layout user={user} />;
}
function Layout({ user }) {
  return <Sidebar user={user} />;
}
function Sidebar({ user }) {
  return <UserMenu user={user} />;
}
function UserMenu({ user }) {
  return <span>{user.name}</span>; // finally used here
}

This is fragile — every intermediate component must be aware of data it doesn't care about. Context eliminates the middlemen.

Creating and Providing Context

// 1. Create the context — the argument is the default value
// (used only when a component has no Provider above it)
const UserContext = React.createContext(null);

// 2. Provide the value — any descendant can now read it
function App() {
  const user = useAuth();
  return (
    <UserContext.Provider value={user}>
      <Layout />    {/* no prop drilling — Layout doesn't receive user */}
    </UserContext.Provider>
  );
}

Consuming Context with useContext

// 3. Any descendant reads it directly
function UserMenu() {
  const user = useContext(UserContext); // zero props passed down
  return <span>{user.name}</span>;
}

The component subscribes to the context. Whenever the Provider's value changes, UserMenu re-renders automatically.

The Re-render Behaviour — The Most Important Thing to Understand

This is the most common source of performance bugs with Context. Every component that calls useContext(MyContext) re-renders whenever the context value changes — even if the specific data it uses didn't change.

// ❌ Performance trap: a new object is created on every App render
// Every consumer re-renders even if user and theme haven't changed
function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  return (
    <AppContext.Provider value={{ user, theme, setUser, setTheme }}>
      <Everything />
    </AppContext.Provider>
  );
}

The value prop creates a new object every render, so every consumer thinks the context changed — even if user and theme are identical. React uses Object.is to compare context values.

Fix: Memoize the context value

// ✅ Stable object reference — consumers only re-render when user or theme changes
function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const value = useMemo(() => ({ user, theme, setUser, setTheme }), [user, theme]);
  return (
    <AppContext.Provider value={value}>
      <Everything />
    </AppContext.Provider>
  );
}

Better fix: Split contexts by update frequency

// Split frequently-changing from rarely-changing values
// Components that only need theme don't re-render when user changes
<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    <Everything />
  </ThemeContext.Provider>
</UserContext.Provider>

Context vs Redux / Zustand — When to Use Each

Context is often misused as a replacement for a state management library. They serve different purposes:

  • Context — great for stable, rarely-changing ambient data that many components read: current user, theme, locale, feature flags. Every consumer re-renders on any change, so frequent updates hurt performance.
  • Redux / Zustand — great for frequently-changing, complex global state. External stores allow components to subscribe to specific slices — a component watching cart.total doesn't re-render when user.preferences changes. They also come with devtools, time-travel debugging, and middleware.
Context is not a state management library — it's a dependency injection mechanism. The question is not "Context or Redux?" but "how often does this value change and how many components subscribe to it?"

The Default Value Trap

The default value passed to createContext(defaultValue) is used only when a component calls useContext with no matching Provider above it in the tree. It is not the initial value of the Provider. This trips up many developers:

const ThemeContext = React.createContext('light'); // default = 'light'

// This component uses 'light' because there's no Provider above it
function Orphan() {
  const theme = useContext(ThemeContext); // 'light'
  return <div>{theme}</div>;
}

// But this gets 'dark' from the Provider — the createContext default is ignored
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Orphan /> {/* gets 'dark', not 'light' */}
    </ThemeContext.Provider>
  );
}

Common Misconceptions

⚠️

Many developers treat Context as a drop-in replacement for Redux — Context is a dependency injection mechanism, not a state management library. It has no selector system, so every subscriber re-renders on any value change. For frequently-changing global state, an external store like Zustand or Redux is the right tool.

⚠️

Many developers pass a new object literal directly to the value prop — this creates a new object reference on every render, triggering re-renders in all consumers even when the underlying data hasn't changed. Always memoize the value object with useMemo.

⚠️

Many developers think the createContext default value is like an initial state — the default value is only used when useContext is called without a matching Provider anywhere above it in the tree. It's a fallback for orphaned consumers, not the starting value of the Provider.

⚠️

Many developers wrap the entire app in a single all-purpose context with everything in it — this maximises re-renders because every change (user, theme, language, permissions) causes every consumer to re-render. Split contexts by update frequency so consumers only re-render for the values they actually care about.

⚠️

Many developers think Consumer components are required for reading context — useContext() makes Consumer render-prop components completely unnecessary in functional components. useContext is simpler, more readable, and should always be preferred in functional components.

⚠️

Many developers think Context solves all prop drilling problems — Context is the right tool when many unrelated components need the same data. When two siblings need to share state, lifting state to their common parent is simpler and more explicit than adding a Context.

Where You'll See This in Real Code

Authentication: the current user and auth state are read by dozens of components (nav, profile, permissions checks). A UserContext at the app root eliminates passing user as a prop through every layout and page component.

Theming: a ThemeContext holding 'light' or 'dark' mode is consumed by every styled component and UI library wrapper. The theme rarely changes, making Context ideal — consumers re-render only on explicit theme toggle.

Internationalisation (i18n): the current locale and translation function live in a context so any component can call t('key') without receiving the translator as a prop.

Feature flags: a FeatureFlagContext holds the current user's enabled features, consumed by any component that needs to show or hide UI — without every intermediate component knowing about feature flags.

Compound component patterns: libraries like Radix UI and Headless UI use Context internally to share state between parent and child components (e.g. Tabs.Root shares selected tab state with Tabs.Trigger and Tabs.Content) without exposing it as props.

React Router: the router uses Context internally to share the current location, params, and navigation function — that's why useLocation() and useNavigate() work from any depth in the tree without prop threading.

Interview Cheat Sheet

  • createContext(defaultValue) — defaultValue used ONLY when no Provider exists above the consumer
  • Provider: &lt;MyContext.Provider value={...}&gt; — all descendants can read this value
  • Consumer: const value = useContext(MyContext) — subscribes; re-renders when value changes
  • Re-render rule: ALL consumers re-render whenever the Provider's value changes (Object.is comparison)
  • Performance fix 1: memoize value object — useMemo(() => ({ a, b }), [a, b])
  • Performance fix 2: split contexts — separate UserContext and ThemeContext so consumers only re-render for their slice
  • Context use cases: stable ambient data — current user, theme, locale, feature flags
  • Not a good fit: frequently updating state (counters, filters) — use Zustand/Redux for those
  • No useContext needed for write-only access — pass setState functions directly as props instead of via context
💡

How to Answer in an Interview

  • 1.Lead with the problem it solves: 'Context solves prop drilling — passing data through intermediate components that don't use it. Instead of threading a prop through 5 layers, you broadcast the value from a Provider and any descendant reads it directly with useContext.'
  • 2.The re-render behaviour is the depth question: 'Every component that calls useContext re-renders when the context value changes — even if the specific field it reads hasn't changed. This is why passing a plain object as value causes all consumers to re-render on every App render. The fix is useMemo on the value object, or splitting into separate contexts by update frequency.'
  • 3.The Context vs Redux answer interviewers want: 'Context is dependency injection, not state management. It has no selector system — every consumer re-renders on any change. I use Context for stable ambient data (user, theme, locale) and Zustand/Redux for frequently-changing global state where selective subscriptions matter.'
  • 4.Mention the default value trap: 'The argument to createContext is a fallback used only when there's no Provider above the consumer in the tree — not an initial value. Most developers never intentionally trigger it; it mainly exists to make TypeScript happy and catch missing Providers.'
  • 5.Splitting contexts shows senior thinking: 'I split contexts by update frequency — a ThemeContext and UserContext instead of one AppContext. A button that reads theme doesn't re-render when the user logs out. This is the simplest way to control which consumers are affected by which state changes.'
  • 6.Connect to React Router and libraries: 'Context is the mechanism behind useLocation(), useNavigate(), and most React library hooks — they all use a hidden Provider at the top of the tree and useContext in the hook. Understanding Context is understanding how most of the React ecosystem works.'
📖 Deep Dive Articles
React Context API vs Redux: When to Use Which in 20257 min readReact State Management in 2025: useState, useReducer, Context & Beyond10 min readTop 50 React Interview Questions (2025 Edition)15 min read

Practice Questions

No questions tagged to this topic yet.

Related Topics

useState Hook — Complete React Interview Guide
Beginner·4–8 Qs
Custom hook — Complete React Interview Guide
Intermediate·4–8 Qs
React Rendering & Performance Interview Questions
Advanced·4–8 Qs
useContext Hook — Complete React Interview Guide
Intermediate·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz