React

Explain the composition pattern in React

The short answer

Composition in React means building complex UIs by combining simpler components together, rather than using inheritance to extend behavior. Components don't need to know what their children will be ahead of time — they accept other components as props or children and render them. This is how you build flexible, reusable UI in React, and it's the pattern the React team explicitly recommends over inheritance.

The children prop — React's primary composition tool

The most basic form of composition uses the children prop. When you nest JSX inside a component's opening and closing tags, React passes that nested content as props.children:

function Card({ children }) {
return <div className="card">{children}</div>;
}

function App() {
return (
<Card>
<h2>Welcome back</h2>
<p>You have 3 new notifications.</p>
</Card>
);
}

Card doesn't know or care what's inside it. It provides a styled wrapper, and you fill in the content. This is containment — the component contains whatever you put inside it.

This is incredibly powerful because it means Card is endlessly reusable. It can contain a user profile, a product listing, an error message, or anything else. The component is generic by design.

Containment — components that don't know their children

Some components serve as generic containers. Think layouts, modals, sidebars, and dialogs. They define the visual structure but have no idea what content will live inside them:

function Modal({ children, isOpen }) {
if (!isOpen) return null;

return (
<div className="modal-overlay">
<div className="modal-content">{children}</div>
</div>
);
}

function App() {
return (
<Modal isOpen={true}>
<h2>Confirm deletion</h2>
<p>Are you sure? This action cannot be undone.</p>
<button>Delete</button>
</Modal>
);
}

The Modal component handles the overlay, positioning, and structure. The content is entirely up to the consumer. This separation means you build the modal once and use it everywhere with different content.

Specialization — making specific versions of generic components

Specialization is when you create a more specific component by wrapping a generic one with particular content or configuration:

function Dialog({ title, message, children }) {
return (
<div className="dialog">
<h2>{title}</h2>
<p>{message}</p>
{children}
</div>
);
}

function WelcomeDialog() {
return (
<Dialog
title="Welcome!"
message="Thanks for signing up for our service."
>
<button>Get started</button>
</Dialog>
);
}

function DeleteConfirmDialog({ onConfirm }) {
return (
<Dialog
title="Delete item?"
message="This action cannot be undone."
>
<button onClick={onConfirm}>Confirm</button>
</Dialog>
);
}

WelcomeDialog and DeleteConfirmDialog aren't subclasses of Dialog. They're specific configurations of it. The generic Dialog is composed with particular props and children to create specialized versions. No inheritance needed.

The slots pattern — multiple composition points

Sometimes a component needs more than one "hole" to fill. The children prop gives you one slot, but you can create as many as you need using named props:

function PageLayout({ header, sidebar, children }) {
return (
<div className="page">
<header className="page-header">{header}</header>
<div className="page-body">
<aside className="page-sidebar">{sidebar}</aside>
<main className="page-content">{children}</main>
</div>
</div>
);
}

function DashboardPage() {
return (
<PageLayout
header={<Navigation />}
sidebar={<DashboardFilters />}
>
<DashboardCharts />
<RecentActivity />
</PageLayout>
);
}

PageLayout defines the structure with three slots: header, sidebar, and the main children area. Each slot can be filled with entirely different components depending on the page. The layout doesn't know about navigation or dashboards — it just knows where to put things.

Composition solves the problems people reach for inheritance

You might wonder: why not use inheritance? If WelcomeDialog is a specific kind of Dialog, shouldn't it extend it?

In class-based OOP, you might think that way. But in React, inheritance creates tight coupling and limits flexibility. Let's say you want a Dialog that sometimes has a footer and sometimes doesn't, sometimes has an icon and sometimes doesn't, sometimes wraps in a modal and sometimes renders inline. With inheritance, you'd need an increasingly complex class hierarchy with method overrides.

With composition, you just pass different children and props:

function App() {
return (
<Dialog title="Settings saved">
<p>Your preferences have been updated.</p>
<footer>
<button>OK</button>
</footer>
</Dialog>
);
}

No subclassing, no overriding render methods, no super() calls. You compose pieces together like building blocks. This is why the React docs explicitly say: "We haven't found any use cases where we would recommend creating component inheritance hierarchies."

Composition for dependency injection

One of the most practical uses of composition is avoiding prop drilling. Instead of passing data through layers of components that don't need it, pass the already-rendered component:

// Without composition — drilling `user` through everything
function App({ user }) {
return <Layout user={user} />;
}
function Layout({ user }) {
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
return <UserAvatar user={user} />;
}

// With composition — pass the rendered component directly
function App({ user }) {
return (
<Layout
sidebar={
<Sidebar avatar={<UserAvatar user={user} />} />
}
/>
);
}

Now Layout and Sidebar don't need to know about user at all. They just render whatever they receive. The data stays close to where it's used.

Why interviewers ask this

Composition is React's core design philosophy. Interviewers ask about it because it reveals whether you build components that are flexible and reusable, or rigid and tightly coupled. Understanding composition means you can build design systems, avoid inheritance traps, and solve prop drilling elegantly. It's one of those concepts that separates developers who use React from developers who think in React.