React composition interview questions

Reviewed by Mark Dickie · Last updated

React composition patterns are techniques for building flexible UIs by combining smaller components through props like children, render functions, and shared context instead of deep prop drilling or inheritance. For an interview on this topic, expect questions about when to reach for compound components, how render props compare to hooks, and where the context API fits into a component tree. You should be able to explain the trade-off between prop drilling and context, write a children-based slot pattern from memory, and reason about what makes a component reusable across different product surfaces.

Here is a quick map of the main composition patterns you will see in questions:

PatternCore ideaWhen it shines
children propPass JSX into a component as a built-in propLayout wrappers, cards, dialogs
Render propsPass a function as a prop that returns JSXSharing reusable data or rendering logic before hooks
Compound componentsSeveral components share hidden context to coordinate stateSelect, tabs, accordion
Higher-order componentA function that wraps a component to inject props or behaviorCross-cutting concerns like logging or auth
Context + compositionProvider components expose state to descendants without prop drillingTheming, locale, feature flags

What does a React composition interview test?

Interviewers use composition questions to check three things at once:

  1. Whether you can avoid prop drilling by lifting shared state into context and consuming it only where needed.
  2. Whether you can design an API that stays flexible for callers, like a Dialog that lets callers place arbitrary content into named slots.
  3. Whether you know the failure modes, such as a compound component that breaks when a child renders outside the provider, or an HOC that loses display names and ref forwarding.

How do render props compare to hooks?

Render props were the dominant pattern for sharing stateful logic before hooks landed in 16.8. A component accepts a function prop and calls it with the data it manages. Hooks replaced most of these use cases because they let you extract logic into a plain function (useFetch, useTheme) without adding a wrapper component to the tree. You still see render props in interviews because they test whether you understand JSX-as-data and can reason about a callback that returns elements. A good answer notes that hooks are simpler for logic sharing, but render props remain useful when a component needs to control how part of its subtree is rendered, such as virtualized lists or data-table cell renderers.

What makes a compound component reusable?

Compound components coordinate through a shared context so each piece reads the same state without passing props to every child. A Tabs might expose Tabs.List, Tabs.Tab, Tabs.Panel, all reading an index from context. The payoff is that callers can compose the pieces in any order and add wrapper elements between them. The catch is that any consumer rendered outside the provider loses access to that shared state, so libraries often throw a clear error or fall back to a default. Knowing where that boundary breaks is a common follow-up question.

Key facts

  • Tarmac has 16 React interview questions on this topic, 10 of them on this page, at difficulty 1–4 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 492 job postings as of August 2026.
  • Tarmac last reviewed these React interview questions on 31 August 2026.

At a glance

Questions10 shown · 16 in the bank
Difficulty1–4 of 5
FormatsMultiple choice, Flashcard, True / false, Short answer, Ordering, Multiple answer, Find the bug

What you'll review

  1. composition
  2. patterns

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

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

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 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).

Sources

The official documentation these questions are checked against:

Related interview questions

Job market

See react salaries and hiring demand from live job postings.

The other 6 questions

This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 6 and keeps every answer. 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.