While-loops considered harmful

Your phone buzzes with the server alert ring-tone. The director of customer support taps you on the shoulder: "Um, it looks like we're down." Not a single screen in your web app loads. You attempt to SSH into your server but connecting takes for...ev...er. Finally you connect to the shell and see the CPU pegged at 100%. You tail the access logs, but nothing looks fishy. Site traffic is normal. Frantic and desperate, you reboot the server. The server comes back up ... but immediately the CPU spikes back to 100%.

An hour later you find the problem. That hour seemed like an eternity as you tore your hair, fended off account managers, grepped every line of source you could find, and wondered what it feels like to get fired. And what was the error? It was a while loop that processed some unexpected input and spun forever.

Infinite loop bugs are among the most insidious and - outside of wiping out customer data - most destructive bugs. In web application development, most bugs only affect the particular code path where the buggy code resides. Most of the time you know what paths are most critical and can give those paths an extra thorough review. But a while-loop bug in some unimportant, admin-only screen can disable the entire application for everyone. When the problem strikes, it is very difficult to figure out what exactly is causing the app to freak out and where the buggy code lives.

Fortunately, there is a solution to avoid this class of bug completely: never write a while-loop.

Anywhere in your code where the need arises for a while loop, instead write a "for-loop" with a sensible maximum.

For example, let us say that I am doing a standard chunked-file read in python. In my younger days I would write:

f = open('file.mp3', 'rb')
while True:
    chunk = f.read(4000)
    if not chunk:
        break
    output.write(chunk)

A better pattern is:

# pick a number comfortably higher than the
# logic of the function needs to support.
EMERGENCY_BRAKE = 10000000
for x in xrange(0, EMERGENCY_BRAKE):
    chunk = f.read(4000)
    if not chunk:
        break
    output.write(chunk)
    if (x+1) >= EMERGENCY_BRAKE:
        raise IndexError("File reading loop hit the emergency brake")

Even better, you can write a helper method:

def iter_with_brake(max_times=1000000):
    for x in xrange(0, max_times + 1):
        if x >= max_times:
            raise IndexError("Iteration exceeded maximum")
        yield x

And now you can use this helper any time you would have previously used a while-loop:

for x in iter_with_brake():
    chunk = f.read(4000)
    if not chunk:
        break
    output.write(chunk)

In the trivial case of reading a file, the lack of an emergency brake is unlikely to cause a problem. But when you start creating more complicated while-loops, such as for tree traversal, streaming parsers, etc., while-loops become very dangerous. I therefore highly recommend eliminating them entirely.

(Note for the pedantic: this blog post targets web application programmers. There are of course cases in software development where while loops are still fine. If you are writing the main event loop for a sensor on the next Pioneer space probe, your while-loop should happily keep plugging away until the end of the universe).