The assumption from the previous piece has a favorite hiding place: a loop that looks like it costs one unit of work per item, but actually costs one unit per item per some other thing, and that second thing was small enough at the time to disappear from view. A loop that calls a lookup function on a list, where the lookup itself scans a second list — that’s not N operations, it’s N times M, and if M was five at the time nobody noticed, because five times anything is still small. The code reads as a simple pass over the data. What it’s actually doing is a hidden multiplication, and multiplication is exactly the kind of thing that stays invisible right up until both sides of it grow.

This is worth separating from the general assumptions problem because it has a specific, recognizable signature: performance that’s fine, then fine, then fine, and then suddenly isn’t — not gradually degrading but falling off a cliff, because the growth is quadratic or worse and quadratic growth looks flat for a long time before it doesn’t. A team can watch a system handle steadily increasing load for months with no sign of trouble, then hit a wall in a single week, and the instinct is to look for what changed recently. Usually nothing did — the code has been quadratic the whole time, and the input simply crossed the point where the squared term stopped being dominated by everything else. The bug was there from day one; it just wasn’t expensive yet.

The reason this specific shape is so easy to miss is that it doesn’t look like a mistake anywhere in isolation. The outer loop is obviously necessary — you do need to process every item. The inner lookup is obviously necessary too — you do need to find the related record. Each piece, read on its own, is doing exactly what it should. The problem only exists in the combination, in the fact that the inner operation’s cost scales with the same thing the outer loop scales with, and that relationship is invisible unless you’re specifically looking for it — it doesn’t show up in a code review focused on whether each line is correct, only in one focused on how the total cost grows as both dimensions grow together.

The practical defense is cheap relative to the cost of finding this in production: whenever a loop contains an operation whose own cost depends on the size of some collection, ask explicitly whether that collection grows with the same thing the outer loop grows with. If a lookup inside a per-user loop scans a per-user-sized list, that’s the shape, and it’s worth naming even when today’s numbers make it harmless — the same instinct as writing down the assumption itself, just applied to complexity rather than to a fact. The fix is usually mundane once spotted: index the thing being looked up, so the inner operation stops scaling with size, and the hidden multiplication collapses into two things that each scale on their own instead of one thing that scales as their product.

This is really a special case of the thread’s larger point: the code isn’t wrong, it’s scoped to an input size where the hidden cost never mattered, and nothing about its outward behavior signals that the scope has an edge. The difference here is that the edge has a distinctive shape once you know to look for it — flat, then a cliff — which means it’s one of the more findable instances of an assumption quietly waiting to expire. Reading a loop and asking “what does the thing inside this loop cost, and does that cost depend on what the loop depends on” is a small habit that catches a disproportionate share of the surprises this thread has been describing.