Intermediate0 questionsFull Guide

React Higher Order Components (HOC) — Complete Interview Guide

Master Higher Order Components — how they work, the conventions that prevent hard-to-debug bugs, why hooks largely replaced them, and the cases where HOCs are still the right choice.

The Mental Model

A Higher Order Component is a function that takes a component and returns a new, enhanced component. Think of it as a component factory with an extra layer of behaviour baked in — authentication checking, loading states, analytics tracking, or feature flags — without the original component knowing anything about it. The wrapped component just receives props and renders; the HOC handles the cross-cutting concern around it.

The Explanation

The Pattern

A HOC is a function: it accepts a component as an argument and returns a new component that wraps it with additional logic. The naming convention is to prefix with with:

// HOC that redirects unauthenticated users
function withAuth(WrappedComponent) {
  return function AuthGuard(props) {
    const { user, loading } = useAuth();

    if (loading) return <Spinner />;
    if (!user)   return <Navigate to="/login" />;

    return <WrappedComponent {...props} />;
  };
}

// Usage
const ProtectedDashboard = withAuth(Dashboard);

// ProtectedDashboard behaves exactly like Dashboard, but checks auth first
<ProtectedDashboard userId={123} />

The Three Conventions You Must Follow

1. Pass through all props

// ❌ Breaks the wrapped component — props are swallowed
return <WrappedComponent />;

// ✅ Always spread props through so the wrapped component gets everything
return <WrappedComponent {...props} />;

2. Set a displayName for debugging

function withAuth(WrappedComponent) {
  function AuthGuard(props) { ... }

  // React DevTools will show "withAuth(Dashboard)" instead of "AuthGuard"
  AuthGuard.displayName = `withAuth(${WrappedComponent.displayName ?? WrappedComponent.name})`;

  return AuthGuard;
}

3. Forward refs

HOCs break ref forwarding by default — a ref on a HOC-wrapped component points to the HOC wrapper, not to the underlying component's DOM node. Fix this with React.forwardRef:

function withLogging(WrappedComponent) {
  const WithLogging = React.forwardRef((props, ref) => {
    logRender(WrappedComponent.name);
    return <WrappedComponent {...props} ref={ref} />;
  });
  WithLogging.displayName = `withLogging(${WrappedComponent.name})`;
  return WithLogging;
}

Composing Multiple HOCs

HOCs compose by nesting function calls — but deep nesting creates "wrapper hell" and makes debugging painful:

// Hard to read and debug — which HOC caused the bug?
const EnhancedComponent = withRouter(withAuth(withAnalytics(withTheme(Dashboard))));

// Use a compose utility (from Redux or Ramda) to flatten the nesting
const enhance = compose(withRouter, withAuth, withAnalytics, withTheme);
const EnhancedComponent = enhance(Dashboard);

Even with compose, HOC-wrapped components are hard to see in React DevTools because each HOC adds a layer to the component tree.

The Problems With HOCs

  • Prop naming collisions — if two HOCs inject a prop with the same name, one silently overwrites the other. Debugging this is painful.
  • Wrapper hell — composing 4–5 HOCs produces deeply nested DevTools trees that hide the component you actually care about.
  • Opaque data flow — it's not clear from the component definition which props came from HOCs vs were passed by the parent.
  • Ref forwarding complexity — every HOC must explicitly forward refs or they get lost, and this is easy to forget.

HOC vs Custom Hook — The Modern Choice

Custom hooks solve most of what HOCs were used for — without the component nesting, prop collision, or ref forwarding complexity:

// HOC approach — adds a component layer, injects props
const ProtectedDashboard = withAuth(Dashboard);

// Custom hook approach — logic lives inside the component, no wrapping
function Dashboard() {
  const { user, loading } = useAuth(); // same logic, no wrapper
  if (loading) return <Spinner />;
  if (!user) return <Navigate to="/login" />;
  return <DashboardContent />;
}

Hooks compose naturally, don't add tree depth, can't collide on prop names, and are visible in the component's own code. For new code, prefer custom hooks over HOCs.

When HOCs Are Still the Right Choice

  • Class components — hooks don't work in class components. HOCs remain the only way to inject hook-based logic into a class component you can't convert.
  • Existing HOC-heavy codebases — if your codebase heavily uses connect() from Redux v4, withRouter, etc., HOCs are already the established pattern.
  • Third-party library APIs — some libraries still export HOCs as their primary API. Understanding HOCs is required to use them correctly.
  • React.memo — technically a HOC; wrapping a component in React.memo to control re-renders is a HOC usage that still makes sense.

Common Misconceptions

⚠️

Many developers think HOCs are deprecated or wrong — they're a valid pattern, especially when working with class components or HOC-based libraries like Redux v4's connect(). React.memo is itself a HOC. They're less necessary in modern functional-component codebases, but not deprecated.

⚠️

Many developers forget to spread props in a HOC and wonder why the wrapped component isn't receiving its props — the most basic HOC mistake is returning <WrappedComponent /> without {...props}. The HOC swallows everything and the wrapped component gets nothing.

⚠️

Many developers don't set displayName and then can't debug their apps — React DevTools shows 'Component' or a random function name instead of 'withAuth(Dashboard)'. Always set displayName explicitly; it takes one line and saves hours of debugging.

⚠️

Many developers don't realise HOCs break refs by default — a ref attached to a HOC-wrapped component points to the HOC's function component, not the underlying DOM node or class instance. Every HOC that might have a ref applied to it must use React.forwardRef.

⚠️

Many developers use HOCs when a custom hook would be simpler — if the logic doesn't need to render anything (auth check, data fetching, event listeners), a custom hook expresses it more clearly with no component nesting, no prop injection, and no ref forwarding complexity.

⚠️

Many developers define HOCs inside another component's render — defining a HOC inside render creates a new component type on every render, which makes React unmount and remount the wrapped component on every parent re-render. Always define HOCs at the module level, outside of any component.

Where You'll See This in Real Code

Redux connect(): the classic HOC — connect(mapStateToProps, mapDispatchToProps)(MyComponent) wraps a component to inject Redux store state and dispatch as props. Still present in millions of React codebases.

Route protection: withAuth(Dashboard) is a ubiquitous pattern in older Next.js and React Router codebases — check authentication, redirect if not logged in, render the page if authenticated.

React.memo: React.memo(ExpensiveComponent) is a built-in HOC — it wraps the component and skips re-renders when props haven't changed. The fact that a standard React API is a HOC shows the pattern is still valid.

Error tracking HOCs: some error monitoring SDKs provide a withErrorTracking(Component) HOC that wraps any component to automatically report errors thrown during its render lifecycle.

A/B testing: a withVariant(Component, 'experiment-name') HOC injects the current experiment variant as a prop, letting components render variant-specific UI without managing experiment state themselves.

Storybook decorators: Storybook's story decorators are essentially HOCs applied to stories — they wrap each story in a provider (theme, router, Redux store) so each story has the context it needs to render.

Interview Cheat Sheet

  • HOC = function that takes a component, returns a new component: function withX(Wrapped) { return function Enhanced(props) { ... } }
  • Always spread props: &lt;WrappedComponent {...props} /&gt; — never swallow them
  • Always set displayName: Enhanced.displayName = `withX(${Wrapped.displayName ?? Wrapped.name})`
  • Always forward refs: wrap with React.forwardRef if the HOC might receive a ref
  • Naming convention: prefix with 'with' — withAuth, withTheme, withAnalytics
  • Composing multiple HOCs: use compose() utility to avoid deep nesting
  • Don't define HOCs inside render — creates a new component type every render, causing constant unmounts
  • HOC vs custom hook: if no JSX wrapping needed, custom hook is simpler with no tree depth added
  • Still valid for: class components, React.memo, legacy codebases, Redux connect()
💡

How to Answer in an Interview

  • 1.Define it cleanly: 'A HOC is a function that takes a component and returns a new component with added behaviour. It's the decorator pattern in React — you enhance a component without modifying it.'
  • 2.The three conventions show you've used HOCs in production: '(1) always spread props through with {...props}; (2) set displayName so DevTools shows withAuth(Dashboard) not AuthGuard; (3) use React.forwardRef if refs might be passed — HOCs break ref forwarding by default.'
  • 3.The 'HOC vs custom hook' question is almost always asked — give the nuanced answer: 'Custom hooks replaced most HOC use cases because they add no component nesting, can't collide on prop names, and don't need forwardRef. But HOCs are still correct for class components (hooks don't work there), React.memo, and existing HOC-based library APIs like Redux connect().'
  • 4.The 'never define HOCs inside render' rule is a strong signal: 'Defining a HOC inside render creates a new component type on every render cycle, causing React to unmount and remount the wrapped component every time the parent renders. HOCs must be defined at module scope.'
  • 5.React.memo as a HOC example shows breadth: 'React.memo(Component) is a built-in HOC — it wraps a component to skip re-renders when props are unchanged. That a standard React API is implemented as a HOC shows the pattern isn't going away.'
  • 6.Close with when you'd personally choose each: 'In new functional-component code, I default to custom hooks — less complexity, no wrapping. I reach for HOCs when working with class components, using HOC-based library APIs, or applying React.memo.'
📖 Deep Dive Articles
Top 50 React Interview Questions (2025 Edition)15 min read

Practice Questions

No questions tagged to this topic yet.

Related Topics

Custom hook — Complete React Interview Guide
Intermediate·4–8 Qs
React Rendering & Performance Interview Questions
Advanced·4–8 Qs
useRef Hook — Complete React Interview Guide
Beginner·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