What is prop drilling
Passing props through multiple intermediate components that don't use them, just to reach a deep descendant. It makes code verbose, tightly couples components, and is fragile to refactor. Fixes: Context, composition (children/slots), or a state library — pick by how the data is used.
Prop drilling is passing data through layers of components that don't actually need it, purely to get it to a deeply nested component that does.
The problem
<App user={user}>
<Layout user={user}> {/* Layout doesn't use user */}
<Sidebar user={user}> {/* Sidebar doesn't use user */}
<Profile user={user} /> {/* finally — Profile uses it */}Layout and Sidebar are just pass-through couriers. The deeper the tree, the worse it gets.
Why it's bad
- Verbose & noisy — every intermediate component's props are cluttered with things it ignores.
- Tight coupling — intermediate components now "know about"
usereven though they don't use it; they can't be reused or moved easily. - Fragile refactors — moving
Profileor adding a layer means re-threading props through every level. - Hard to trace — where did this prop come from, 5 levels up?
Note: prop drilling one or two levels is fine — it's explicit and clear. It's only a smell when it's deep and many props.
The fixes — pick by how the data is used
1. Context — for data that's genuinely "global" to a subtree and used in many places: theme, current user, locale, auth. The provider sits high, consumers read directly via useContext — no threading. (Watch the re-render caveat.)
2. Component composition / children / slots — often the best fix and underused. Instead of Sidebar forwarding a prop, pass the rendered element down as children:
<Layout>
<Sidebar>
<Profile user={user} /> {/* user passed where it's created, not drilled */}
</Sidebar>
</Layout>Layout and Sidebar just render {children} — they never see user. This removes the coupling without Context's machinery.
3. A state library (Zustand/Redux/Jotai) — for complex, cross-cutting app state used widely.
Choosing
- A couple of levels → just drill, it's fine.
- Truly tree-global, read in many spots → Context.
- The intermediate components are just structural wrappers → composition / children.
- Complex shared client state → a store.
The framing
"Prop drilling is threading a prop through intermediate components that don't use it, just to reach a deep child. It makes those middle components verbose, tightly coupled to data they don't care about, and fragile to refactor. A level or two is fine; deep is the smell. The fixes depend on usage: Context for genuinely tree-global data like theme or auth; component composition — passing the element as children so wrappers never see the prop — when the intermediates are just structural; and a state library for complex shared state. Composition is the most underused fix."
Follow-up questions
- •When is prop drilling actually fine?
- •How does passing children avoid prop drilling?
- •When would you use Context vs composition vs a store?
- •What's the downside of solving everything with Context?
Common mistakes
- •Reaching for Context for everything, including data only one child needs.
- •Not knowing the composition/children fix.
- •Treating any prop passing as 'drilling' — a level or two is fine.
- •Drilling deeply instead of recognizing the smell.
Performance considerations
- •Prop drilling itself isn't a perf problem — but new prop references each render can defeat memoization in intermediates. Context's fix has its own caveat: all consumers re-render on value change.
Edge cases
- •A prop needed by two distant cousins — composition may not reach both.
- •Frequently-changing drilled data — Context re-render concerns.
- •Intermediate components that DO use part of the data.
Real-world examples
- •Threading the current user through Layout → Sidebar → Profile, solved with an auth Context.
- •Compound components using composition so wrappers don't forward props.