Say you’ve done the honest thing and measured, and you know where the time goes. There’s still a choice about how to make it faster, and the instinct is usually to make the slow part quicker — tighten the loop, cache a value, rewrite the hot function in something lower-level. Sometimes that’s the answer. But the largest speedups in practice almost never come from making each step cheaper. They come from changing how the total work grows with the size of the input — from doing fundamentally less, not from doing the same amount faster.

The reason is arithmetic that doesn’t care about your cleverness. A constant-factor improvement makes something twice as fast, or ten times, and that’s a fixed multiplier no matter how big the input gets. A change in the growth rate — from checking every pair of items to checking each item once, from a lookup that scans a list to one that hits a hash — changes the shape of the cost, so the gap widens without bound as the data grows. On a hundred items the clever constant-factor version might win. On a hundred thousand, the better-shaped version wins by a margin that no amount of low-level tuning could ever close. At scale, the exponent beats the coefficient, always.

This is why the most valuable performance fixes are usually the least glamorous: someone finds an accidental quadratic and makes it linear. The nested loop that looked innocent because the inner collection was small in testing, and grew with the data in production. The membership check done against a list inside a loop, turning what should be one pass into N passes. The classic database version — the N+1 query, where fetching a list and then querying once per item turns a single round trip into hundreds. None of these are exotic. They’re the ordinary way performance quietly rots: work that scales worse than anyone noticed, because at the size you tested, everything scales fine.

What makes this the natural sequel to measuring is that the profiler doesn’t just tell you where the time goes — it tells you, if you watch across different input sizes, how it grows, and that’s the more actionable fact. A function that’s slow but flat is a candidate for constant-factor tuning. A function whose time explodes as the data grows is telling you the shape is wrong, and no amount of tuning the inner step will save it — you have to change the algorithm or the access pattern. Knowing which of those two situations you’re in is the difference between a fix that helps a little forever and a fix that keeps the system from falling over next year.

And it points at where to spend the complexity budget from the last piece. Optimization costs clarity, so you want to spend that cost where it buys the most, and a change of shape buys far more than a change of constant — often it’s even simpler, because replacing a nested scan with a hash lookup or a batched query can be clearer than the thing it replaced, not murkier. So the order holds: make it work, make it clear, measure to find what’s actually slow — and when you fix it, look first at how the work grows, not at how to shave each step. The biggest, most durable wins are almost always about doing less, not about doing the same thing faster.