Jiby's toolbox

Jb Doyon’s personal website

Embrace the Early Exit

Posted on — Feb 1, 2024

In programming, there are patterns everywhere: patterns of building code, patterns for organising components, even patterns for how to test things. A whole industry is looking for patterns to solve all their problems.

But amongst them all, there’s one pattern I whole-heartedly love, it’s the Early Exit. In this short and sweet post, I want to declare my undying love to the Early Exit, sharing my excitement.

From If+else to ladders and conditionals hell

Functions routinely need to check some edge case isn’t hit, usually with more edge cases than actual routine happy-path code lines.

The most obvious way to write this is to have if for the case that is good, and else the cases that aren’t.

def change_blogpost(post, user, content_change):
    if user.is_admin():
        return change_content(post, content_change, who=user.id)
    else:
        raise NotAuthorizedError("User is not admin")
Code Snippet 1: Hypothetical piece of code, dealing with admins alone being able to change posts.

Now, once this specific “if” is sorted, the code for when everything goes according to plan is inside an if, so it is shifted “inwards” by one layer of indentation.

Of course, with more things to check, this gets more elaborate, an issue called various names like if-else ladders, or conditional hell.

def change_blogpost(post, user, content_change):
    if user.is_admin():
        if post.published:
            # The main point of this function is here:
            return change_content(post, content_change, who=user.id)
        else:
            raise ContentNotPublishedError("Draft article cannot be changed")
    else:
        raise NotAuthorizedError("User is not admin")
Code Snippet 2: More elaborate checks

The entire structure becomes unwieldy, as we hide the very core of the function in a scaffolding that isn’t easily scannable from afar.

Exit Early!

Early exit is a practice that favours getting the wrong case out of the way early, exiting as fast as possible from the function, returning or erroring as needed, in order to progressively weed out the edge cases, and allow us to focus on the happy path.

Because the bad case has already returned, the further we are in the function, the more things we can assert about the current state of the world.

def change_blogpost(post, user, content_change):
    if not user.is_admin():
        raise NotAuthorizedError("User is not admin")
    # We KNOW user is admin if we reach here
    return change_content(post, content_change, who=user.id)
Code Snippet 3: First example, rewritten as early exit
def change_blogpost(post, user, content_change):
    if not user.is_admin():
        raise NotAuthorizedError("User is not admin")
    # We KNOW user is admin if we reach here
    if not post.published:
        raise ContentNotPublishedError("Draft article cannot be changed")
    # User is admin + post published already:
    return change_content(post, content_change, who=user.id)
Code Snippet 4: More elaborate checks, early-exit style. Notice how with each comment, we build up more certainty about the state of the system we're in, allowing us to pick the right action.

Note that the resulting code avoids multiple levels of indentation, dealing with one thing at a time, ending up with a shorter solution that reads more like a sentence.

This allows developers to focus on the specific case being dealt with on the specific line being worked on, instead of what combination of if/else ladder got us to be on this particular line.

Early Exit, forever!

That’s all for early exit: I haven’t come across any situation that wasn’t improved by flipping an if/else into an early exit, which makes it a bit of an underrated superpower.

I hope I’ve communicated the pleasure of flipping complex conditionals around, and hope you get as much mileage as I do out of this silly little trick.

Small insights like these remind me how much fun it is to play with computer programs.