The fix is almost always small. You delete a useMemo that was never helping, or you hoist a network call out of a loop, or you find a global event listener that some innocent hook installs a hundred times. The diff is three lines. The investigation that found the three lines took a day. And the thing that nags me, every time, is that the engineer who wrote the slow version was not careless. They were good. They were just looking at their own feature, not at the system the feature lived in.
That gap — between "good engineer, looking carefully" and "globally slow code" — is the whole story. Shu Ding's essay Performance Is Not a Technical Problem names the gap precisely: context does not scale. I want to take that sentence seriously and follow it further than performance, because once you see it you start seeing it everywhere.
Locally optimal, globally slow
Here is the uncomfortable claim. A team of skilled engineers, each making the locally correct decision, will produce a slow system by construction. Not as a failure mode — as the default.
The reason is cognitive, not technical. To write code that is globally fast, you have to hold the global picture: every other place that calls this function, every render that this object participates in, every consumer of the abstraction you're about to reuse. But you are shipping one feature. Your working memory is full of that feature. The rest of the system is, charitably, a fuzzy diagram you saw in onboarding. So you optimize what you can see, and what you can see is local.
The essay gives a clean example: a reusable popup hook that attaches one global event listener. In isolation, fine — one listener is free. Reused in a hundred components, it is a hundred listeners all firing on every scroll, and the person who wrote the hundred-and-first usage has no way to see that cost. The abstraction did its job: it hid the implementation. It also hid the price.
That is the part worth sitting with. We treat abstraction as an unalloyed good — "clean," "reusable," "DRY." But an abstraction is a contract that says you don't need to know what's inside. Performance is precisely the property that depends on what's inside. So a clean abstraction and a visible cost structure are in direct tension. The cleaner the seam, the more invisible the multiplication on the other side of it.
The contracts that fail silently
There's a second flavor, and it's nastier because nothing yells at you. Some optimizations are held together by an invariant that the type system never checks.
Take React's referential-equality caches. useMemo compares its dependencies with Object.is; React.cache keys on argument identity the same way. The official docs for cache spell out the trap directly — passing an object that is "a new object reference on every render" produces nothing but cache misses. Your code compiles. Your tests pass, because tests assert behavior, and the behavior is still correct — the cache just quietly stopped caching. You shipped a memoization that does negative work: all of the allocation, none of the savings.
Nobody made a mistake you could point at in review. Someone three months ago wrote a memoized helper that depended on a stable object. Someone last week passed it an object built inline. Both diffs are reasonable. The bug lives in the space between the two diffs — exactly the space no single person was holding in their head. That's context-does-not-scale again, wearing a different costume.
Same disease, three names
Now the turn I actually wanted to make. Performance is not special. It's one member of a family, and the family resemblance is the root cause.
Think about accessibility. A div with an onClick works perfectly for the developer who wrote it, mouse in hand. It is invisibly broken for a keyboard or screen-reader user the developer never simulated. The defect is, again, a gap between the local view (my mouse, my screen) and the system's real surface (every input modality, every assistive technology). No amount of caring fixes it, because caring operates on what you can see.
Think about security. The classic injection bug is a developer who trusted an input because, in the flow they were imagining, the input came from a trusted place. The attacker's whole job is to supply the context the developer didn't hold — the path through the system that wasn't in anyone's head. "Validate untrusted input" fails for the same reason "be mindful of performance" fails: it asks one person to model the entire system at the moment they are concentrating on one corner of it.
Performance, accessibility, security. Three "qualities," one mechanism: each degrades as systemic entropy outruns any individual mental model. They are all emergent properties — they live in the relationships between parts, not in the parts — which is why they cannot be owned by the author of any single part. And it's why the most common organizational response, "we'll train people to be more careful," reliably disappoints. Training raises the ceiling on what one head can hold. The problem is that the system grows past any ceiling.
The cures are a confession
So what works? The essay's answer is to stop relying on attention and instead "distill the patterns" into systems that enforce them at the moment of writing. Lint rules. CI gates. Review checklists that teach. Codemods. And — increasingly — agents that read the whole repository so you don't have to.
I agree, and I want to point at what that agreement admits. Every one of those tools is an external memory that holds the context the human cannot. A lint rule is institutional knowledge that doesn't quit when the senior engineer does. A CI gate is the global view, applied automatically, to a person who only has the local view. We reach for them precisely because we've conceded that "just be careful" was never going to scale with the team.
The most striking example shipped recently. The React Compiler reached 1.0 in October 2025, and its whole pitch is that it does the memoization for you — automatically, at build time, in cases manual useMemo literally cannot reach. Read it through the lens above and it's not a performance tool. It's an organizational one. It takes a quality that depended on every developer holding the render graph in their head, and moves it into a system that holds the render graph instead. The same release pushed more correctness rules into eslint-plugin-react-hooks. Same move: encode the context, stop asking the human to carry it.
That's the honest shape of the thing. We didn't make engineers smarter. We built scaffolding that knows the parts of the system the engineer was always going to forget. The arrival of capable AI agents is the next rung of the same ladder — not because the agent is wiser, but because it can re-read the whole codebase on every change, which is the one thing a busy human provably cannot.
Which leaves a question I don't have a clean answer to. If every durable quality eventually has to migrate out of human discipline and into the tooling, then the real engineering skill is less "write the fast code" and more "build the system that makes slow code hard to write." That's a different job than the one most of us think we signed up for. I suspect it's the more important one.