Hero image for "Context Isn't Free, and Props Aren't the Problem — Your API Shape Is"

Context Isn't Free, and Props Aren't the Problem — Your API Shape Is


Here's the failure mode I keep seeing: a team hits prop drilling pain, reaches for Context, and then six months later they're debugging mystery re-renders across half the component tree. They solved the symptom and created a different problem. The real issue was never props vs. context — it was that the component API was shaped wrong from the start.

I wrote about the render cost side of this back in June. This is the other half: what the prop drilling debate actually reveals about your abstraction decisions, and how to read those signals before you reach for the wrong fix.

Prop Drilling Is a Symptom, Not the Disease

When you're passing a theme prop through three components that don't use it just to reach a Button at the bottom, that's uncomfortable. The standard diagnosis is "prop drilling problem, apply Context." But the discomfort is actually telling you something more specific: your component tree is coupled in a way that doesn't match your data's actual ownership.

The question worth asking before you wire up a Provider is: why does this data need to travel this far? If theme is genuinely global and stable — the kind of thing that changes once per session, if ever — then yes, Context is the right call. The guidance here is sound: use Context for global, stable data like themes or auth state; avoid it for high-frequency updates that will trigger cascading re-renders across every consumer.

But a lot of prop drilling pain isn't about genuinely global data. It's about data that belongs to a local subtree — a form, a modal, a card — being passed through intermediate components because the component boundaries were drawn in the wrong place. Context doesn't fix that. It hides it.

The Abstraction You Extracted Too Early Is the Real Culprit

Here's the pattern that actually creates most prop drilling pain in design systems: you extracted a shared component before you understood what it needed to share.

A recent piece on DEV Community revisiting Sandi Metz's "Wrong Abstraction" argument puts it precisely: abstractions don't start wrong, they start reasonable. A Card component with 80% shared structure gets extracted, parameterized, and shipped. Six months later it has eight variants, a suppressDefaultPadding escape hatch, and props that exist purely to thread data down to a grandchild component that the original design never anticipated.

That suppressDefaultPadding prop isn't prop drilling in the traditional sense — it's the abstraction fighting back. The component is trying to expose internal state to its consumers because the API shape assumed a simpler world. You can solve this with Context (wrap the Card's internals in a local context, let the grandchild consume it directly), but you're now using Context to paper over an abstraction that was wrong to begin with.

The compound component pattern exists precisely for this case. Instead of a monolithic Tabs component with tabPosition, tabStyle, showIcons, and renderTabLabel props all threading data downward, you get <Tabs>, <Tabs.List>, and <Tabs.Panel>components that share state implicitly through a local context while giving consumers explicit control over composition. The prop drilling disappears because the API shape now matches how the data actually flows.

The tradeoff is real, though: compound components are harder to document, harder to constrain, and harder to hand to a junior developer who just needs to drop in a tab group. Flat prop APIs are easier to understand at a glance. The question is whether your team's velocity is more constrained by API complexity or by the escape hatches you're adding to work around a rigid abstraction.

The Signal That Tells You Which Problem You Have

Before you refactor toward Context or toward compound components, it's worth distinguishing between two different kinds of pain:

Pain from data traveling too far through a stable tree — this is the classic prop drilling case, and Context is a reasonable fix if the data is genuinely global and low-frequency. Segment your contexts by domain, keep providers as low in the tree as they can go, and wrap every useContext call in a custom hook so consumers aren't coupled to the context shape directly.

Pain from an abstraction that doesn't fit its use cases — this is what the wrong abstraction argument is really about. When you find yourself adding props to a component not because consumers need to configure it, but because internal subcomponents need data that the API wasn't designed to expose — that's the signal. The fix isn't Context. The fix is reconsidering the component boundary.

The expensive mistake is treating both as the same problem. Context added to a broken abstraction doesn't make the abstraction less broken; it makes the breakage harder to see.


The prop drilling debate has a clean answer that most teams reach for too fast: "just use Context." The messier, more useful answer is that prop drilling tells you where your component tree's seams are under stress. Sometimes the seam needs a bridge. Sometimes it needs to be redrawn.

Watch for the suppressDefaultPadding props in your own codebase. They're not asking for Context. They're asking for a different shape.