Skip to solution
mediumFrontend

Explain the concept of 'composition' in React components.

969 views
01

Understand the problem

Question presented to candidate: "React has no component inheritance. So how do you share and extend behaviour — and what does 'composition over inheritance' mean in practice here?"

What a strong answer should cover:

  • Composition means building complex UI by combining components rather than extending them. React has no inheritance model at all, deliberately.
  • The mechanisms: children for arbitrary content, props as slots (passing elements, not just data), render props, and custom Hooks for shared behaviour.
  • The distinction that matters: composition shares structure; custom Hooks share behaviour.
  • Containment (a generic wrapper that does not know its content) versus specialisation (a specific component configuring a generic one).
  • A measurable benefit: a child passed as children is created by the grandparent, so its element is the same object across the parent's re-renders — React bails out of re-rendering it, with no memo needed.
  • This makes composition a legitimate performance technique, not just an organisational one.
  • Why inheritance is avoided: it couples components to a hierarchy, and UI variation is rarely a clean single-axis taxonomy.

Clarifying questions expected:

  • "Are we sharing markup/structure, or behaviour?" — that picks composition versus a custom Hook.

Code / implementation expected: Yes — a wrapper taking children, ideally showing the re-render benefit.

compositioncomponentsdesign patternsreusability
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — assumes props and components. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The render counts in section 4 were produced by actually counting renders on

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

Composition cuts re-renders without memo — watch the counters
Run Playground
import { useState } from "react";

let insideRenders = 0;
let outsideRenders = 0;

// A deliberately "expensive" child. It is identical in both arrangements —
// the only difference is WHERE its element gets created.
function Expensive({ tag, count }) {
  // simulate real work
  let x = 0;
  for (let i = 0; i < 200000; i++) x += i;
  return (
    <p style={{ background: "#f6f6f6", padding: 8, borderRadius: 6 }}>
      {tag} rendered <strong>{count}</strong> time(s)
    </p>
  );
}

// (a) The child is created INSIDE the stateful component, so a new element
//     object is produced on every render and React re-renders the subtree.
function Inside() {
  const [n, setN] = useState(0);
  insideRenders++;
  return (
    <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12, marginBottom: 12 }}>
      <h4 style={{ marginTop: 0 }}>Child created inside the stateful parent</h4>
      <button onClick={() => setN(n + 1)}>clicked {n} times</button>
      <Expensive tag="Inside" count={insideRenders} />
    </div>
  );
}

// (b) The stateful component receives the child as its children prop. That element was
//     created by Outside, which never re-renders — so it is the SAME object
//     every time and React bails out of re-rendering it.
function Wrapper({ children }) {
  const [n, setN] = useState(0);
  return (
    <div style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12 }}>
      <h4 style={{ marginTop: 0 }}>Child passed in as children</h4>
      <button onClick={() => setN(n + 1)}>clicked {n} times</button>
      {children}
    </div>
  );
}

function Outside() {
  outsideRenders++;
  return (
    <Wrapper>
      <Expensive tag="Children" count={outsideRenders} />
    </Wrapper>
  );
}

export default function App() {
  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6 }}>
      <Inside />
      <Outside />
      <p style={{ color: "#666", fontSize: 13 }}>
        Click both buttons a few times. The first counter climbs on every click;
        the second stays at 1. No React.memo anywhere — only a change in where
        the element is created.
      </p>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 39 of 119 decoded in the React.js track. One more won't hurt.

Back to track