Early return pattern

Originally published Dec 21, 2022·Tagged #software-architecture, #code-patterns

Revision is an important part of writing prose. Often, an author will write their initial draft in a stream-of-consciousness fashion, dumping words on the page in the order that they come to mind. (In fact, that's exactly how I wrote the first draft of this article.) However, to aid understanding, it's important to go back and review what's been written with a more critical eye. Writing and editing are very different disciplines, to such a degree that a college professor once stated "write drunk, edit sober". When editing, you're no longer primarily thinking about yourself and the ideas you want to convey; instead, your focus is on the audience. How will the reader understand this passage? Do these concepts make sense to someone who's seeing them for the first time ever?

Lately I've been thinking about the similarities between writing prose and writing code. I may write more on this later, but I'd like to learn more about the similarities between programming language design and linguistics. One thing is clear to me, however: code produced during stream-of-consciousness writing benefits from a cleanup pass.

One of my favorite cleanup techniques is the "early return" pattern. This is used in a function to reduce the depth of nested logic. Here's an example of what a Mars rover might have in its codebase:

function decideAction() {
  if (boulderAhead()) {
    takePhoto();
    turnLeft(90);
  } else {
    collectSample('soil');
    accelerate();
  }
}

Notice that all of the logic here is indented twice: once for the function body, and once for the if/else statement. However, a simpler version might look like:

function decideAction() {
  if (boulderAhead()) {
    takePhoto();
    turnLeft(90);
    return;
  }
  collectSample('soil');
  accelerate();
}

This adjustment achieves a couple of goals:

  • It emphasizes the "boulder ahead" branch as a special case. With this version, it's clear that soil sample collection and acceleration are the expected "default" behavior. This implies that finding a boulder is a special event, not the norm. (If the opposite were true, we could invert the condition and put takePhoto() and turnLeft() into the unindented function body.)
  • It reduces the nesting depth of the code. This limits the amount of complexity that the reader has to hold in their head at one time. If they're not explicitly interested in the "boulder ahead" case, then they don't need to occupy precious brain space with it.

The "younger brother" of early return: early continue

You can also use this pattern in for or while loops! In this case, the continue keyword in a loop behaves the same as return in a forEach callback. Example:

// Without early continue:

while (true) {
  if (fuelRemaining === 0) {
    soundAlarm('fuel_exhausted');
    await refueling();
  } else {
    computeNavigation();
    fireRockets();
  }
}

// Using early continue:

while (true) {
  if (fuelRemaining === 0) {
    soundAlarm('fuel_exhausted');
    await refueling();
    continue;
  }
  computeNavigation();
  fireRockets();
}

See also