In a React app a `user` object is passed from `App` through `Layout` and `Sidebar` down to `Avatar`, and the two middle components never read it. How does restructuring those components around `children` remove the drilling, and where does the technique stop helping?
answer
- the middle layers never use it
- build the element where the data is
- containers place content, don't inspect it
- only works for a fixed call site
- lists and routes defeat it
basics
~20 sHave Layout and Sidebar accept children instead of the data, and let App build the finished element itself. The prop then crosses zero intermediate components. It stops helping once the consumer's position is decided deep inside — inside lists, routes or recursive trees the middle layer renders.
solid answer
~50 sInvert who creates the element. `Layout` and `Sidebar` stop taking `user` and take `children` (or named slots) instead; `App`, which already holds the data, writes `<Avatar user={user} />` in its own JSX and passes that finished element down as content. The intermediate components become content-agnostic — they position what they are given and never see `user`, so the prop travels zero layers and adding a second consumer costs nothing. The limit is structural: this only works when the owner of the data sits above the layout and the consumer's place in the tree is fixed at that call site. Once the deep component is rendered *by* the middle layer — a row inside a list it maps over, a route element, a recursive tree node — the owner cannot construct it in advance, and the answer becomes a shared-value mechanism rather than composition.
code
jsx · 22 linesfunction Layout({ children }) {
return <div className="page">{children}</div>;
}
function Sidebar({ children }) {
return <aside>{children}</aside>;
}
function Avatar({ user }) {
return <img src={user.avatarUrl} alt={user.name} />;
}
export default function App() {
const user = { name: 'Ada', avatarUrl: '/a.png' };
return (
<Layout>
<Sidebar>
<Avatar user={user} />
</Sidebar>
</Layout>
);
}go deeper
Recognise the smell — components declaring a prop only to hand it straight on — and know that letting a component accept children instead of the data is one way to avoid it.
Explain the inversion precisely: the element is created where the data lives and passed down as content, so intermediate components lose the prop entirely and become reusable containers.
Diagnose before prescribing. Check who owns the data and whether the consumer's position is fixed, apply composition when both hold, and name the structural cases — lists, routes, recursion, many scattered readers — where it cannot work.
Own the boundary policy: decide as a team when a value is passed as content, when it is published for descendants to read, and when it belongs in an external store, so the codebase does not accumulate three inconsistent answers to the same question.
## What prop drilling actually is Drilling is not "passing props" — it is passing props through components that have no use for them. Each intermediate layer gains a parameter it must declare, type, forward and keep in sync, purely as transport. The cost is not performance; it is that changing the deep component's needs edits every file in the chain, and each intermediate signature lies about what the component depends on. ```jsx function App() { const [user] = useUser(); return <Layout user={user} />; } function Layout({ user }) { return <aside><Sidebar user={user} /></aside>; } function Sidebar({ user }) { return <Avatar user={user} />; } // finally used here ``` ## The restructure: pass content, not data Make the middle layers containers. They accept `children` and place it; they do not know what it is: ```jsx function App() { const [user] = useUser(); return ( <Layout> <Sidebar> <Avatar user={user} /> </Sidebar> </Layout> ); } function Layout({ children }) { return <div className="page">{children}</div>; } function Sidebar({ children }) { return <aside>{children}</aside>; } ``` `user` is now read where it is created and consumed in one step. `Layout` and `Sidebar` have no `user` prop at all, and never will — the next component that needs `user` is added at the top, not threaded through. The idea generalises: the element `<Avatar user={user} />` is an inert description object, so it can be created at the top of the tree and rendered near the bottom. Position in the *rendered* tree and position in the *code* stop being the same thing. ## Named slots when the layout has several holes If `Sidebar` also needs a footer region, do not go back to threading data — add another node prop: ```jsx <Sidebar footer={<PlanBadge plan={user.plan} />}> <Avatar user={user} /> </Sidebar> ``` The middle component still knows nothing about `user`. ## What it costs Be honest about the tradeoffs, because interviewers push here: - **The top component's JSX grows.** Deep nesting in one place can turn `App` into a large, structurally busy render. Extract named consts or intermediate components that still own their own data. - **It moves ownership up.** The data must be reachable at the call site. If `user` is fetched inside `Layout`, this restructure is not available at all. - **It couples the top to the layout.** `App` now spells out the nesting; a layout change is an edit at every call site rather than inside `Layout`. That is usually a fair trade for a handful of call sites, and a bad one for dozens. ## Where it stops working The technique fails whenever the owner of the data cannot write the element in advance: - **The consumer is rendered by the middle layer.** A table maps over rows and renders `<Row />` itself; `App` cannot hand it a pre-built row per record it has never seen. - **Position depends on runtime routing.** The component that needs the value is whatever the router matched, so no static call site can construct it. - **Recursive or unbounded depth.** A tree renders itself; there is no top-level place to put every descendant. - **Many scattered consumers.** A theme or locale is read by dozens of leaves at unknown depths; passing each as content would mean hoisting the entire tree into one file. - **The intermediate layer genuinely needs the value.** Then it is not drilling — the prop is used, and forwarding it is correct. In those cases the right answer is a mechanism that lets a descendant read a value published by an ancestor, or an external store for larger app state — how those work, and how to choose between them, is a separate topic in its own right. ## How to answer Diagnose first: count how many layers merely forward the prop and check whether the data's owner sits above the layout. If both hold, restructure with `children` — it is the cheapest fix, adds no new abstraction, and makes the intermediate components more reusable as a side effect. If they do not hold, say so explicitly and reach for a shared-value mechanism instead. A candidate who reaches for one immediately, without asking whether composition would have solved it, is skipping the simpler answer.
- How do you tell real prop drilling from an ordinary prop chain that is fine?Count the layers that only forward. A prop each layer actually reads is normal data flow, not drilling. One pass-through layer is usually not worth restructuring either. The smell is three or more components declaring, typing and forwarding a prop they never touch — and the tell is that adding a field to that object edits every file in the chain.
- What does the restructure cost the top-level component?Its JSX absorbs the nesting the middle components used to own, so the layout structure is now spelled out at the call site — and repeated at every call site. With a handful of pages that is a fair trade for content-agnostic layouts; with dozens it becomes duplication, and a layout component that encapsulates the structure again is the better answer.
- Why does a list defeat this technique?Because the consumer is created by the middle layer, not by the owner. A table maps over rows it fetched or received and renders `<Row />` per record, so no ancestor can pre-build those elements — there is no call site where they exist yet. The value has to reach them some other way, typically a shared value the row reads for itself.
saying these in an interview costs you the question
- Reaches for a global mechanism before trying composition
- Thinks any multi-level prop chain counts as drilling
- Claims children composition also solves lists and routes
- Says drilling is mainly a performance problem
- Assumes the data can always be hoisted above the layout