The Code You Can't Loop On
The first two pieces in this thread treated the feedback loop as something you can measure and shorten: a ten-minute cycle you turn into ten seconds. But there’s a worse case than a slow loop, and it’s the one that quietly does the most damage — code that has no real loop at all. You can’t easily run it in isolation, you can’t watch what it does, and when it fails you can’t reproduce the failure on demand. A change here isn’t a ten-minute round trip; it’s a guess shipped into the dark, where the only feedback is whether something breaks in production days later, in a way you may not even be able to trace back. This is code you can’t loop on, and it behaves differently from code that’s merely slow to iterate on.
Code ends up in this state for identifiable reasons, and they’re worth recognizing because they’re often accidental rather than essential. It’s tangled into global state or external systems so you can’t stand up just the piece you care about. It depends on timing, concurrency, or the network in ways that make failures show up once in a hundred runs and never when you’re looking. It has no seam where you can observe what’s happening inside — no log, no return value you can inspect, no way to ask it “what did you just do?” without running the entire system and squinting at the outputs. None of these are laws of nature. They’re properties of how the code was built, which means they’re properties you can change, and changing them is often the single most valuable thing you can do to that code.
The reason it matters so much is that unloopable code doesn’t just slow down the work — it degrades the quality of every decision you make about it. When you can’t get fast feedback, you can’t take small steps, so you take large speculative ones and hope. You can’t refactor with confidence, because you have no cheap way to confirm you didn’t break anything, so the code ossifies — everyone touches it as little as possible and only with dread. Bugs in it are expensive to even reproduce, let alone fix, so they linger. The absence of a loop is why some parts of a codebase become permanent sources of fear: not because the logic is especially hard, but because there’s no fast way to learn whether any given change to it was right. Fear of code is very often just the felt experience of a missing feedback loop.
This reframes what tests, logs, and observability are actually for. It’s tempting to think of them as things you add after the code works, to guard against regressions or to satisfy a coverage number. But their deeper purpose is to give the code a loop it didn’t otherwise have. A test is a way to run a piece in isolation and get an instant verdict. A log line is a seam that lets you see inside without stopping the world. A metric is feedback about behavior you otherwise couldn’t observe until a user complained. Seen this way, these aren’t hygiene — they’re the machinery that turns unloopable code into code you can actually work with, and that’s why the code that has them is not just safer but genuinely faster to change.
So the throughline of the whole thread is this: the loop is the unit of speed, shortening it beats optimizing inside it, and the worst-off code is the code with no loop to shorten. When something in a codebase feels frightening or perpetually slow to touch, the most useful question isn’t “how do I make this change carefully?” but “why can’t I get fast feedback here, and what would it take to give this code a loop?” Building that loop — a seam to test against, a way to reproduce the failure, a window to see inside — is usually the highest-leverage work available, because everything else you’ll ever do to that code has to pass through it. Code you can loop on is code you can improve. Code you can’t is code you can only fear.