React Patterns Interview Questions

Reviewed by Mark Dickie · Last updated

React patterns are reusable solutions to common problems that come up when building component-based UIs — how to share state, compose components, and avoid prop drilling without making the code harder to read. For a patterns-focused React interview, you should know compound components, the render prop technique, higher-order components (and why they fell out of favor), custom hooks as the modern abstraction layer, and the trade-offs between lifting state up versus reaching for Context. Interviewers also test whether you can explain why a pattern fits a given problem rather than just naming it — expect to compare approaches on re-render cost, prop drilling depth, and testability.

PatternWhat it solvesKey trade-off
Compound componentsFlexible composition of related parts (e.g. <Tabs><Tab/></Tabs>)More boilerplate; requires React Context or cloneElement to share internal state
Render propsSharing rendering logic between componentsCan cause prop-inversion confusion; largely replaced by hooks
Higher-order components (HOC)Wrapping a component to inject props or behaviorHard to type in TypeScript; naming collisions with displayName; replaced by hooks in most cases
Custom hooksExtracting and reusing stateful logicCaller owns the rules-of-hooks contract; no guard against conditional calls
Context + useReducerGlobal-ish state without prop drillingEvery consumer re-renders on value change unless you split contexts or memoize

What does a React patterns interview actually test?

You will usually get a mix of "explain this pattern" and "refactor this code using X pattern" questions. The interviewer wants to see that you can pick the right tool and defend the choice. Common areas:

  1. Compound components — build a Select or Accordion where the parent owns open/closed state and children stay presentational. You will need React.Children.map or Context to wire them together.
  2. Custom hooks — extract a useFetch or useToggle from a component that mixes data and presentation. Expect follow-ups on cleanup, dependency arrays, and why hooks cannot be called conditionally.
  3. Render props vs. hooks — explain why hooks replaced render props for most use cases, and identify the cases where a render prop is still the better fit (e.g. injecting render logic into a third-party library).
  4. HOCs — know what withAuth looks like, but be ready to explain why the React team moved away from HOCs: they compose poorly, wrap displayName awkwardly, and lose static methods.
  5. State management patterns — when to lift state up, when to split a context into provider and consumer pieces, and when a reducer inside Context beats useState at the top level.

How do I decide between Context and a state library?

Context is fine for low-frequency updates — theme, auth, locale. It falls down when the context value changes often, because every component that reads that context re-renders on each change. If you find yourself splitting Context into three or four smaller contexts just to avoid re-renders, that is the signal to reach for a real state library like Zustand, Jotai, or Redux Toolkit. The pattern interview usually stops at the split-context approach, but mentioning the library trade-off shows you have shipped real apps.

What re-render pitfalls come up in pattern questions?

Memoization is the recurring trap. useMemo and useCallback only help when the memoized value is passed to a child that is wrapped in React.memo or used as a dependency of another hook. Passing a fresh object literal as a prop to a memoized child breaks the memo because {} !== {} on every render. When you build compound components or custom hooks, the interview will check whether you return stable references for objects and functions, not just whether the component "works."

Key facts

  • Tarmac's React interview questions cover 18 questions at difficulty 1–5 of 5.
  • Tarmac tracked 2,587 job postings asking for React in August 2026.
  • Roles asking for React advertise a median base salary of US$175,000, across 568 job postings as of August 2026.
  • Tarmac last reviewed these React interview questions on 31 August 2026.

At a glance

Questions18
Difficulty1–5 of 5
FormatsMultiple choice, Flashcard, True / false, Multiple answer, Short answer, Ordering, Find the bug

What you'll review

  1. composition
  2. patterns
  3. higher order components
  4. render props

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

React/patterns/composition

A <Card> component renders <div className="card">{children}</div>. What does this composition pattern achieve?#

Options

Show answer

The parent passes arbitrary JSX into Card through props.children, so Card wraps content it never needs to know about. Callers nest any markup between the <Card> tags, and the component drops it in place via {children}. This composition keeps the wrapper agnostic about its contents and avoids prop-drilling configuration down through it.

Why:

props.children lets a component act as a generic wrapper: callers nest any JSX between its tags and the component places it via {children}. This composition avoids prop-drilling configuration and keeps the wrapper agnostic about its contents.

React/patterns

In React, what does "favor composition over inheritance" mean in practice?#

Show answer

Build components by nesting and combining other components — passing components or JSX in via children or other props — to reuse and customize behavior, rather than creating a subclass hierarchy of components. React's component model has no built-in concept of one component inheriting from another's rendered output; composition (wrapping, slotting, and configuring via props) is how code and behavior get reused instead.

Why:

This is a foundational React mental model, not just a style preference: the framework gives you no extends SomeOtherComponent mechanism for sharing rendered UI, so wrapping and configuring components via props and children is the idiomatic — and often the only practical — way to reuse and customize behavior across components.

React/patterns

A <Layout> component is used like <Layout header={<TopNav />} sidebar={<SideNav />}>{mainContent}</Layout> and renders each prop into a different region of the page. What is this technique, and how does it go beyond relying on children alone?#

Options

Show answer

header and sidebar are ordinary props whose values happen to be JSX; naming them gives Layout several independent insertion points instead of the single children slot. A prop's value can be a JSX element just as easily as a string, so Layout reads props.header and props.sidebar and places each wherever it wants, exactly like props.children. The difference from plain children is that a component only gets one children slot per usage — naming additional props is how a component exposes more than one region for the caller to fill. Nothing about this is automatic, string-keyed, or restricted to class components.

Why:

There is nothing special about header or sidebar — they are props like any other, and a prop's value can be a JSX element just as easily as a string or number. Layout reads props.header and props.sidebar and places each wherever it wants in its own markup, exactly the way it would place props.children. The difference from plain children is that a component gets only one children slot per usage, so a component needing several independent regions (a page header, a sidebar, a main area) names extra props to expose more than one slot. Nothing is automatic (b) — React does not inspect nested JSX and route it anywhere; the component author decides what each prop means. Slot content is a real React element, not a lookup key (c). And any component type — function or class — can be the value of a prop; there's no restriction tied to how the passed-in component itself is defined (d).

React/patterns

A HOC returns function (props) { /* … */ return <Component {...props} />; } with no further changes. In React DevTools, the wrapped component shows up as an anonymous function, making it hard to tell which HOC wraps which component once several are nested. What's the idiomatic fix?#

Options

Show answer

Set the returned wrapper function's displayName (for example WithAuth(${getDisplayName(Component)})), so DevTools shows the wrapping chain by name. DevTools reads a component's name from its function or class name, or from displayName when present, falling back to a generic label otherwise. Naming the wrapper after both the HOC and what it wraps keeps a stack of nested HOCs legible instead of anonymous. Renaming the original component doesn't affect the new wrapper DevTools inspects, key is a reconciliation hint unrelated to display labels, and DevTools naming can absolutely be controlled — that's the point of the convention.

Why:

React DevTools reads a component's name from its function/class name or, when present, its displayName property, and falls back to a generic label when neither tells it anything useful. The convention is to set displayName on the returned wrapper to something like WithAuth(CommentList) — naming both the HOC and what it wraps — so a stack of nested HOCs reads as a legible chain instead of a pile of anonymous entries. Renaming the original component (b) doesn't touch the new wrapper function DevTools is actually inspecting. key (c) is a reconciliation hint for lists and has nothing to do with the DevTools display label. And DevTools naming absolutely can be controlled this way (d) — that's the entire point of the displayName convention.

React/patterns

When a child component accepts value and onChange props instead of managing its own internal state, the parent — not the child — owns and is the source of truth for that piece of state.#

Options

Show answer

True. When a child accepts value and onChange instead of managing its own state, the parent's state is the single source of truth and the child is just a view onto it — that's what makes the component "controlled." The opposite composition is uncontrolled, where the child owns the state internally and the parent reads it only on demand, typically through a ref. A given component shouldn't switch between the two ownership models across its lifetime.

Why:

This is exactly what makes a component "controlled": the child receives its current value as a prop and reports changes through a callback, but never stores that value itself, so the parent's state is the single source of truth and the child is just a view onto it. This is the flip side of an uncontrolled composition, where the child owns the state internally (e.g. via its own useState, or the raw DOM for an uncontrolled input) and the parent reads it only on demand, typically via a ref. React's guidance is also that a given component shouldn't switch between the two modes across its lifetime — pick one ownership model and keep it.

React/patterns

What is a compound component in React?#

Show answer

A set of components — e.g. Tabs, Tabs.List, Tabs.Tab, Tabs.Panel — that share implicit state through Context so they coordinate with each other, letting the caller compose them declaratively without manually wiring props like active/onChange between them. Similar to how HTML's <select> and <option> work together: apart they don't do much, but together they form one coherent unit.

Why:

The pattern's value is that the caller writes plain, declarative JSX and the sub-components find each other through context internally — no prop-drilling active/onChange down through every child. It remains a current, actively-used pattern for component libraries (tabs, accordions, menus) even after hooks replaced most HOC/render-prop use cases, because it solves a different problem: coordinating several rendered pieces, not just reusing logic.

React/patterns

A <Tabs> component is used as:#

Options

Show answer

Tabs owns the active-tab state and provides it through React Context; Tabs.Tab and Tabs.Panel read that context internally rather than receiving it as props from the caller. This is the compound-components pattern: the parent owns one piece of state and shares it via a context provider, while the namespaced sub-components (Tabs.List, Tabs.Tab, Tabs.Panel) call useContext to read and register against it, so the caller writes clean declarative markup with no manual prop wiring — similar to how <select> and <option> cooperate in HTML. There is no built-in sibling-state sharing, no DOM-event channel, and no build-time index inference involved.

Why:

This is the compound-components pattern: Tabs is the parent that owns one piece of state (which value is active) and hands it down through a Context provider, while Tabs.List, Tabs.Tab, and Tabs.Panel (namespaced as static properties on Tabs) call useContext internally to read the active value and a setter, and to register themselves. The caller never threads active/onChange between the pieces by hand — it writes clean, declarative markup, similar to how HTML's <select> and <option> cooperate without the page wiring them together. React has no built-in sibling-state-sharing mechanism (b) — Context is a deliberate, explicit API the components must opt into. Communication happens through React's own render/context machinery, not DOM events (c), and the active tab is genuine runtime state set by clicking a tab, not something resolved once at build time (d).

React/patterns

React's own docs describe older codebases as having a "wrapper hell" of providers, consumers, higher-order components, and render props layered around the real UI. What specifically do custom hooks solve that HOCs and render props could not?#

Options

Show answer

Hooks let you reuse stateful logic without adding any extra component to the tree, so there's no added nesting, prop-forwarding, or naming collisions. HOCs and render props share logic by wrapping — a HOC returns a new component and a render prop renders a function's output — so every reused behavior adds a layer to the tree, which is the "wrapper hell" React's own docs describe. A custom hook is a function call inside the component that needs the logic, so composing several hooks means calling several functions, not nesting wrapper components. Hooks don't offer a rendering-speed advantage, aren't tied to whether a build step exists, and weren't the only way to share logic between class and function components — just the way that avoids wrapping.

Why:

HOCs and render props share logic by wrapping — a HOC returns a new component, and a render-prop component renders a function's output — so every reused behavior adds a layer to the component tree. React's hooks documentation names this directly: "Hooks allow you to reuse stateful logic without changing your component hierarchy." A custom hook is just a function call inside the component that already needs the logic, so composing several hooks means calling several functions in one function body, not nesting several wrapper components — which is exactly what removes the wrapper-hell nesting, the prop-forwarding boilerplate, and the prop-name collisions HOCs are prone to. Nothing in the hooks model claims a rendering-speed advantage (b) — the win is architectural, not a performance one. Hooks and HOCs are unrelated to whether a build step exists (c); both run in ordinary React apps with or without a bundler. And HOCs/render props could already share logic between class and function components by wrapping either kind (d) — hooks aren't the only route, just the one that avoids wrapping.

React/patterns

A component shares its internal mouse position via <MouseTracker>{(pos) => <Cursor pos={pos} />}</MouseTracker>. Which of these are real downsides of composing several render-prop components together this way? Select all that apply.#

Options

Pick every one that applies.

Show answer

Three are real downsides: nesting several render-prop components produces a deeply indented "callback pyramid" that's harder to scan than the equivalent hook-based logic; defining the render function inline on every render creates a new function reference each time, which can defeat React.PureComponent/React.memo on the child — a caveat React's own render-props docs flag directly; and each render-prop component is still a real component in the tree, adding to the same DevTools nesting HOCs are criticized for. It is false that a render prop can only pass a single primitive — it can pass an object or several arguments — and false that a render-prop component can't use hooks internally; there's no exclusivity between how a component exposes output and what it uses to compute it.

Why:

React's render-props docs themselves flag (b) as a caveat: "using a render prop can negate the advantage that comes from using React.PureComponent if you create the function inside a render method," because a new function is created every render, and the shallow prop comparison sees a changed prop every time. Nesting several such components (a) produces the same kind of deep, hard-to-read structure React's hooks docs describe as "wrapper hell," which explicitly lists render props among its causes — and each render-prop component (c) is a real component instance in the tree just like a HOC's wrapper, adding the same nesting cost. Neither remaining option is true: a render prop's function can receive any value — an object, several arguments, whatever the component chooses to pass (d is false) — and a render-prop component's internal implementation is free to use useState, useEffect, or any other hook; there is no exclusivity between how a component exposes its output and what it uses internally to compute it (e is false).

React/patterns

Custom hooks largely replaced higher-order components and render props for sharing stateful logic across components. Explain why hooks won out — what did HOCs and render props cost that hooks avoid?#

Show answer

HOCs and render props share logic by wrapping a component in another component, so every reused piece of logic adds a layer to the tree — deep composition produces 'wrapper hell': extra nesting, harder-to-read DevTools trees, and prop-forwarding boilerplate. HOCs specifically also risk prop-name collisions when two wrappers inject the same prop name, and they lose ref access to the wrapped component unless the ref is explicitly forwarded. Custom hooks let a component call a function to reuse stateful logic without adding any component to the tree at all — no wrapping, no naming collisions, no broken refs — and multiple hooks compose by simply calling several of them in one function body instead of nesting wrapper components.

Why:

The core cost of HOCs and render props is that they share logic through wrapping — every reused behavior becomes another component around the real UI, which is exactly what React's docs call 'wrapper hell.' Custom hooks share the same stateful logic through an ordinary function call inside the component that needs it, so composing several reused behaviors means calling several hooks, not nesting several wrapper components. That removes the extra tree depth, the prop-forwarding boilerplate, HOCs' prop-name collisions, and their broken-ref-by-default behavior — all in one move, which is why hooks became the default for new logic-reuse code.

React/patterns

Order these approaches to sharing logic across React components by the era in which each became the community's dominant pattern, earliest first.#

Put these in order

Show answer

Mixins predate higher-order components and render props, which predate custom hooks. Mixins, built into React.createClass, were React's original code-sharing mechanism until the React team's 2016 post "Mixins Considered Harmful" documented their hidden dependencies and naming collisions and recommended higher-order components instead. HOCs and render props then became the standard way to share logic through composition — wrapping a component in another component. Custom hooks, introduced in React 16.8 in 2019, replaced most remaining HOC and render-prop use cases by letting a component reuse stateful logic through a plain function call, with no extra wrapping component added to the tree.

Why:

Mixins were React's original code-sharing mechanism, built into React.createClass, until the React team's 2016 post "Mixins Considered Harmful" documented how they caused hidden dependencies and naming collisions and recommended higher-order components as the replacement. HOCs and render props then became the standard way to share logic across class (and later function) components through composition — wrapping a component in another component or in a function-as-child. Custom hooks arrived with React 16.8 in 2019 and took over most remaining logic-reuse cases, because a hook shares stateful logic through a plain function call with no extra wrapping component in the tree.

React/patterns/higher-order-components

This higher-order component adds a loading prop but the wrapped component stops receiving its other props. What is wrong?#

function withLoading(Component) {
  return function WithLoading(props) {
    const loading = useIsLoading();
    return <Component loading={loading} />;
  };
}

Options

Show answer

The HOC never forwards the incoming props to Component; it should render <Component {...props} loading={loading} />

Why:

The wrapper receives props but only passes loading, swallowing everything the caller provided. A well-behaved HOC spreads the original props through: <Component {...props} loading={loading} />. The wrapper is itself a component, so calling hooks in it is fine.

React/patterns/render-props

What is the render props pattern, and what does it allow you to share?#

Show answer

A render prop is a prop whose value is a function that returns JSX; the component calls it with its internal data so the caller decides what to render. It lets a component share behavior or state — like mouse position or data-loading status — while leaving the markup up to the consumer.

Why:

Render props invert control of rendering: the component owns the logic and hands its data to a function prop (often children) that produces the UI. Custom hooks now cover many of the same cases more ergonomically.

React/patterns

A HOC is defined as function withLogging(Component) { return function Enhanced(props) { /* … */ return <Component {...props} />; }; }. After wrapping, Enhanced.someStaticHelper is undefined, even though Component.someStaticHelper was defined on the original component. What's happening, and what's the fix?#

Options

Show answer

Enhanced is a brand-new function, so it does not automatically inherit Component's static properties — the fix is to copy the needed statics onto Enhanced manually, or run the wrapper through a library like hoist-non-react-statics that copies them all. JavaScript never copies one function's static properties onto an unrelated function just because it renders it, so Component.someStaticHelper still exists on Component, but nothing put it on Enhanced. This is a real production trap: code calling a static convention through the wrapped export silently breaks after a HOC is added, with no error pointing at the cause. There's no reconciler stripping statics, static properties work fine on plain functions, and switching to a class changes nothing.

Why:

withLogging returns a completely separate function object; JavaScript does not copy an object's static properties onto some other, unrelated function just because one renders the other. Component.someStaticHelper still exists — on Component — but nothing put a someStaticHelper property on Enhanced, so accessing it there is undefined. The fix is to copy the statics you need onto the wrapper (Enhanced.someStaticHelper = Component.someStaticHelper) or, more robustly, run the wrapper through hoist-non-react-statics, which copies all non-React static properties automatically. This is a real production trap: code that calls Component.fetchData() or a similar static convention through the wrapped export silently breaks after a HOC is added, with no type or runtime error pointing at the cause. There's no reconciler stripping behavior (b), static properties work fine on plain functions (c) — they're just properties on a function object — and switching to class changes nothing about whether statics are copied (d).

React/patterns

Which of these are genuine pitfalls of the higher-order component pattern that a custom hook avoids? Select all that apply.#

Options

Pick every one that applies.

Show answer

Three are genuine pitfalls: prop-name collisions, where two HOCs injecting the same prop name silently let the last one win with no error; broken ref forwarding, where a ref placed on a HOC-wrapped component attaches to the outer wrapper instead of the inner component unless the HOC explicitly uses React.forwardRef; and wrapper nesting, where each HOC layer adds a real component to the tree, producing the deep DevTools nesting React's docs call "wrapper hell." It is false that HOCs can't access Context — a HOC is an ordinary component and can call useContext like any other — and false that they're incompatible with TypeScript, since generics over React.ComponentType<P> preserve and extend the wrapped component's prop types.

Why:

Prop-name collisions (a) are a documented HOC pitfall: if withStyles and withTheme both inject a style prop, whichever is applied last wins, and the component silently receives the wrong value with nothing to flag it. Ref forwarding (b) genuinely breaks by default — ref isn't a regular prop, so {...props} never carries it through; the wrapper has to explicitly use React.forwardRef to pass it to the wrapped component. Wrapper nesting (c) is real too — each HOC layer is a real component in the tree, and several stacked HOCs produce the deep, hard-to-read nesting React's docs call "wrapper hell." Neither remaining option holds up: a HOC is an ordinary component, so it can call useContext (or render a Context.Consumer) just like any other component (d is false), and HOCs work fine with TypeScript using generics over React.ComponentType<P> to preserve and extend the wrapped component's prop types (e is false).

React/patterns

A custom hook can fully replace a render-prop component whenever the shared behavior only needs to hand back data or callbacks — but if the shared behavior must also inject specific wrapping markup around externally-provided content, a hook alone can't do that, because a hook's return value is data, not rendered output.#

Options

Show answer

True. A hook returns data — state, handlers, computed values — but it does not itself contribute a piece of the render tree the way a component does. If the shared behavior needs to own actual JSX output, such as wrapping caller-supplied content in specific markup or a portal, something still has to be a component that takes children or a render function and decides what to render, which is what a render prop provides. A hook can supply the data behind that decision, but it can't emit the surrounding JSX by itself.

Why:

A hook returns values — state, handlers, computed data — it does not itself contribute a piece of the render tree the way a component does. If the reusable behavior needs to own actual JSX output (wrap children in a positioning <div>, render into a portal, conditionally swap what's displayed around caller-supplied content), something still has to be a component that takes children or a render function and decides what to render — which is exactly what a render prop (or an equivalent children-as-function API) provides. A hook can supply the data driving that decision, but it can't emit the surrounding JSX by itself, so render props (or a plain wrapper component) remain the right tool whenever the pattern needs to control markup, not just hand back values.

React/patterns

A parent tries to focus the wrapped input via inputRef.current.focus(), but inputRef.current is always null. Which line causes this?#

function withTooltip(Component) {
  return function WithTooltip(props) {
    return <Component {...props} />;
  };
}

const EnhancedInput = withTooltip(Input);

function Form() {
  const inputRef = useRef(null);
  return <EnhancedInput ref={inputRef} onFocus={handleFocus} />;
}

Options

Show answer

Line 2 — the wrapper function doesn't accept or forward a ref; because ref isn't a regular prop, {...props} on line 3 never carries it through to Component

Why:

ref is not a regular prop — like key, React handles it specially and it is never included in the props object a component receives, so {...props} on line 3 silently drops it no matter what else the wrapper forwards. WithTooltip (the function returned on line 2) simply has no second parameter to receive a ref in, and nothing routes the ref the caller passed on line 11 down to the real Input. The fix is to wrap the returned function in React.forwardRef((props, ref) => <Component {...props} ref={ref} />), which gives the wrapper a place to receive the incoming ref and pass it through explicitly. React.forwardRef wraps the returned component itself (b mislocates it — it isn't an argument to withTooltip), useRef(null)'s initial value is just a placeholder that React overwrites with the DOM node once something actually attaches to it (c misreads how refs get populated), and refs absolutely can reach through a HOC once forwardRef is used correctly (d is false — that's the entire purpose of the API).

React/patterns

Your app has 40 page components, some from a shared component library your team doesn't own. You need an auth gate applied to all of them without editing each page's source. A withAuthGuard(PageComponent) HOC applies the check by wrapping each page at the route level. Why is this a case where a HOC (or an equivalent wrapping component) is still a better fit than a custom hook?#

Options

Show answer

A custom hook has to be called from inside the target component's own function body, so retrofitting it into 40 components means editing all 40 — including ones you don't own. A HOC (or an equivalent wrapping component used at the route level) applies to a component from the outside, composing around whatever it's given, so the same guard covers pages you own and pages from a library you don't with zero source changes. This is a genuine, still-current advantage of wrapping over hooks: applying cross-cutting behavior uniformly without needing access to the wrapped component's internals. Hooks can read routing or auth state fine, neither pattern is tied to server-versus-browser execution, and there is no call-count restriction on hooks.

Why:

The Rules of Hooks require a hook to be called from within the component's own function body, which means adopting useAuthGuard() in a page you don't control means either forking that component or asking its owner to add the call — neither of which is "without editing each page's source." A HOC (or an equivalent wrapping element used generically at the route level, e.g. <RequireAuth><Page /></RequireAuth>) applies to a component from the outside: it composes around whatever it's given, so the exact same wrapper covers pages you own and pages from a library you don't, with zero changes to any of their source. This is a genuine, still-current advantage of wrapping over hooks — applying cross-cutting behavior uniformly without needing access to (or the ability to edit) the wrapped component's internals. Hooks can read routing/auth state fine via useContext or a routing library's own hooks (b) — that's not the limitation. Neither pattern is tied to server vs. browser execution (c) — that's a Server/Client Component distinction, unrelated to hooks vs. HOCs. And there is no such call-count restriction on hooks (d); a hook can be called from as many components as need it, just as a HOC can wrap as many components as need it.

Sources

The official documentation these questions are checked against:

Related interview questions

Job market

See react salaries and hiring demand from live job postings.

Practise these until they stick

That's every question we hold on this topic, and the page marks what you pick. What it can't do is remember. A free account keeps every answer, and what you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.