Return early and clearly

There are different guidelines out there about where and how many return statements to use in a function, e.g. return only once at the end of the function or return early and often. Which one makes for the most readable code?

A few days ago, I encountered an interesting poll on Twitter:

Where and how to return values from a function is a topic that can lead to passionate and opinionated discussions. As usual, there are pros and cons for many of the different options.

Return as soon as you are ready

I often see functions that start by declaring a result variable and then return it in a single return statement at the end of the function. The argument for this style quite often is that it allows optimizations that are otherwise not possible. Aside from the fact that we should not put small-scale performance before readability in general, this is not true. It might have been decades ago, but optimizers are pretty good in dealing with multiple returns these days. So, unless a profiler tells us otherwise, there are no good reasons for single return statements in complex functions. There are however reasons against that style.

When a function determines a return value and assigns it to a variable instead of immediately returning that value, it may not be clearly visible if that value is altered later in the function or not. In fact, it is not guaranteed that the value is not altered by accident. The immediate return saves us not only from subtle bugs but also saves the reader from having to read through more of the function than necessary.

Handling preconditions

Often, the ability of a function to return a meaningful value depends on the inputs and the environment. If some of those preconditions are not met, the function returns some sentinel value (e.g. a null pointer) or an error code or throws an exception. Conceptually, the naive way to deal with those conditions is to have a bunch of nested conditional blocks that contain the actual work:

Data getSomeData(Configuration const& config) {
  if (theConfigIsOK(config)) {
    if (weCanOpenSomeFile()) {
      if (weCanOpenANetworkConnection()) {
        Data data = tryToParseSomethingFromNetwork();
        if(data.makesSense()) {
          data.addSomeInformation();
          data.beautify();
          return data;
        } else {
          return Data{};
        }
      } else {
        throw NetWorkConnectionError();
      }
    } else {
      // uhm. What was this else for again?
    }
  } else {
    // something about the config, right?
  }
}

The solution can be very elegant: If we invert the logic by checking for the error cases instead of the bad cases, the code becomes much cleaner:

Data getSomeData(Configuration const& config) {
  if (theConfigIsBad(config)) {
    return Data{"BadConfig"};
  }

  if (!weCanOpenSomeFile()) {
    throw FileNotFound{"or something like that"};
  }

  if (!weCanOpenANetworkConnection()) {
    throw NetWorkConnectionError();
  }

  Data data = tryToParseSomethingFromNetwork();
  if(!data.makesSense()) {
    return Data{"parseError"};
  }

  data.addSomeInformation();
  data.beautify();
  return data;
}

Returning early, in this case, has several benefits: Obviously, there is no deep nesting like in the first version. This is enabled because we return early in case of errors: with a return (or throw) in the if block we do not need an else block for the rest of the function. In addition, handling the errors immediately enables us to push them out of our minds. We need not keep track of them to write that later else statement. Last but not least, the actual work is set at the end of the function and not hidden in the middle of that forest of if-else blocks. Arguably, that actual work should be factored out into its own function.

Dealing with alternatives

The second category of conditionally selecting return values arises from having alternative means to calculate those values. The preconditions in the first example determined whether or not a meaningful value can be returned. In this case, there are conditions that determine how the value has to be obtained. If the how is wrapped into its own function as it should be, each condition will have only a single return statement, because there is nothing more to say about the respective case:

SomeData retrieveData(Configuration config) {
  if (config.describesFile()) {
    return retrieveDataFromFile(config.fileDescriptor());
  }

  if (config.describesNetwork()) {
    return retrieveDataFromNetwork(config.networkAddress());
  }

  return SomeData{"BadConfig"};
}
Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

22 Comments


  1. I’ve been in the discussion between “single point of exit” vs. “early return”.

    One of the arguments was “.., so code analysis tools (like LINT) can parse the code more easily”. Sorry, that one doesn’t have any weight in my opinion. I don’t want to have to use a specific code style to circumvent the crappy implementation of a tool. Said implementation is automatically crappy because the implementation details force me to use a specific code style.

    Another argument was actually that it was more readable if you had it that way. This is where we had to leave agreeing to disagree. Here “seniority” and my at that time limited knowledge of the language (C++) played in favor of the colleague.

    Nowadays…
    In my opinion especially for precondition checks early return (or throw) should be used, so you can see the preconditions at the beginning of each function.
    “Later on” in the function I’d actually let it depend on how the rest of the function looks like.
    We still have some early returning functions (breaking our style guide) which actually are so complex that I failed to refactor them multiple times and actually gave up because I simply didn’t understand what it exactly did. Again I’m not that experienced with C++. But some other functions don’t benefit from the result-variable that had to be introduced because they were only 5-7 lines anyway with one or two precondition checks and a calculation.

    So in short my opinion today is: It depends (as it nearly always does with nearly all topics XD)

    Reply

  2. As an aged programmer that has suffered through this debate many, many times… resurrecting one point that was left out of the discussion and disregarding simplistic examples… multiple returns in a method can cause maintenance issues over time. When a modification needs to be made later, either inserting logic or adding additional exit points can more easily and unintentionally change logic. Was there an expectation of order of evaluation? Does the method exit now before some other logic would have modified a result? Of course, these are side effects, or cause side effects and not how any method should be structured. But, multiple return patterns appear to encourage it. There were many tools that would detect multiple exits points and highlight them as structural problems… If I remember correctly, McCabe tools (years ago) would highlight methods with multiple returns as highly complex. Personally, I have preferred methods of single exit point design (detest the while-false-break patterns) and have simply worked to implement them as cleanly as possible. Break out the complexity until the methods are tight and clean. Embracing multiple returns in methods seems counterproductive for that purpose. There are many times, I do want to just add in one or two early pre-condition returns and ask myself… what about my design is causing my code to call this method that can’t be completed properly? Did I take a pointer when an reference would be better? Should I throw an exception rather than return some unreasonable value or an error code disguised as a result? The debate rages on – as I was asked to comment about it on a code review just a day ago.

    return make_optional (preconditions(), result);

    Reply

    1. I think most of the discussion boils down to method complexity. A method with many branches is not maintainable regardless of the number of returns. Changing these methods is risky and error-prone. If the method has a single return but deep nesting any changes are likely to break some logic as it is hard to follow which code was executed in which branch under what condition.

      If the method is simple, e.g. guard checks with early exits (exceptions for precondition violation, return for special cases) it is easy to maintain and I think easier to read than nested ifs.

      I think a good compromise in most cases is to consider returns in the middle of a function suspicious and allow early exits for special cases and precondition checks. This of course does not allow the IMHO much easier version of find which exists in the loop when the element is found. I find this version much more readable than the single-exit version


      template I find(I first, I last, T const& x) {
      for(; first != last; ++first) {
      if (*first == x) {
      return first;
      }
      }

      return last;

      }


      template I find(I first, I last, T const& x) {
      I result=last;

      for(; result!= last; ++first) {
      if (*first == x) {
      result = first;
      break;
      }
      }

      return result;

      }

      The result variable can be skipped and first can be used directly, but this illustrates another point. A major drawback I encountered in a project enforcing single exit was that now suddenly I need to have temporary result variables which need to be initialized. This is quite annoying when the class does not have a default constructor and leads to work-around with unique_ptr or optional.

      Reply

  3. I think the single-exit rule is a historical artifact that should be abandoned. It is interesting to research the origin of the rule. This discussion stackexchange provides interesting historical detail. I think the most interesting ones are:

    “Single Entry, Single Exit” originated with the Structured Programming revolution of the early 1970s, which was kicked off by Edsger W. Dijkstra’s letter to the Editor “GOTO Statement Considered Harmful.”

    “Single Entry, Single Exit” was written when most programming was done in assembly language, FORTRAN, or COBOL. It has been widely misinterpreted, because modern languages do not support the practices Dijkstra was warning against.

    “Single Entry” meant “do not create alternate entry points for functions”. In assembly language, of course, it is possible to enter a function at any instruction. FORTRAN supported multiple entries to functions with the ENTRY statement

    “Single Exit” meant that a function should only return to one place: the statement immediately following the call. It did not mean that a function should only return from one place. When Structured Programming was written, it was common practice for a function to indicate an error by returning to an alternate location. FORTRAN supported this via “alternate return”

    It is also interesting to point out that the current understanding of single exit is probably a remainder of manual resource managing in C or other languages. If you have to manually release resources before exiting a function it is very error-prone to have multiple exits. RAII solves this problem in a very nice way, but there are still people to write manual new/deletes.

    Reply

    1. I find the last quoted block especially scary.

      Reply

    2. Jens,

      I was concerned about what you said:

      “It is also interesting to point out that the current understanding of single exit is probably a remainder of manual resource managing in C or other languages. If you have to manually release resources before exiting a function it is very error-prone to have multiple exits. RAII solves this problem in a very nice way, but there are still people to write manual new/deletes.”

      I do write manual memory allocation code with new and delete here and there and your statement above seems valid for such cases in C++. I was doing lot of functional programming and thought that single exit out of a function seems like a neater way to exit a function.

      Reply

      1. @Arnit I wonder what the motivation behind manual new/delete is? I have not written a manual delete in years. Have you considered make_unique or another RAII wrapper class?

        Reply

        1. Yes, I do have to try RAII and make_unique. My programs are mostly algorithm type code which rather use simple constructs to construct complex code such as simple component functions without much stdlib in them to write complex algorithms which could occupy a few thousand lines. The reason I didn’t try RAII or the other is due to concerns about code size and speed of program execution. However, now with modern C++, perhaps that is not a serious concern.

          Reply

  4. I am of the impression that only one exit out a function is way better than having multiple return statements although the usecase for your suggestion could be strong for having multiple return statements in a function.

    An example is:

    uint32_t Func(uint32_t data)
    {
    uint32_t returnValue = 0;

    if (data)
    {
    returnValue=1;
    }
    else
    {
    returnValue=0;
    }
    return returnValue;
    }

    The above function seems better than

    uint32_t Func(uint32_t data)
    {

    if (data)
    {
    return 1;
    }
    else
    {
    return 0;
    }
    }

    What is your opinion about having a single return or a single exit out of a function?

    Reply

    1. In your example I’d think that data being 0 is a special case, so I’d write the function like this:

      uint32_t Func(uint32_t data) {
        if (!data) {
          return 0;
        }
      
        return 1;
      }
      

      There’s simply no need to load that extra returnValue variable into the readers’ memory.

      Reply

  5. There is also very nice “do-while(0)” technique, which can make multiple conditions check very easy with a use of “break”:

    do
    {

    if( something )
    {
    break;
    }

    if( something_else )
    {
    break;
    }

    }
    while(0);

    Reply

  6. I try to avoid returning any old place in a function because it leads to maintenance errors – a person changing the code doesn’t look for early returns, makes incorrect assumptions, and inserts bugs into the code.

    Reply

  7. I remember that in VB6 our styleguide was very strict on only returning at the end of a function. I believe it was because of how error handling was done in VB6.

    Nowadays, when using a language with Exceptions, a function can actually exit at almost any place. So a single return makes no sense anymore.

    Reply

  8. Succinctness. Economy.
    1) “no deep nesting
    2) “not hidden in the middle of that forest of if-else blocks
    3) “actual work should be factored out into its own function”
    Almost like Occam!
    Keep spreading the word.

    Reply

  9. Hi,

    I am a C# Microsoft Stack Dev and I trust and use the style pointed out in your blog every time and it works great for me

    if (some early-out is true ( like someone passed me a NULL))
    {
    Return something;
    }

    if (some early-out is true)
    {
    Return something;
    }

    Reply

  10. It is insane to even assume that one return style will suit all requirements …

    In applications where every success and failure is audited and entered into a log file, I use a single return statement (or as less as possible) and enter as much information into a LOG file as I can.

    In most applications where there are several validations to performed before core of the function can be activated, I tend to use return statement with each opposite branch of validation so the caller can detect which validation failed.

    Reply

    1. The same here 🙂

      When many many years ago, we have decide to make feature
      “LOG func-calls, params, returns” in our Valentina DB ADKs,

      we have to re-write existed code to single return style.

      Reply

  11. So, 25 years-ago this is what you expected to see:

    Value function(some stuff…)
    {
       //else-if ladder of early-outs.
       if(some early-out is true ( like someone passed me a NULL))
       { 
          Return something;
       }
       else if(some early-out is true)
       {
          Return something;
       }
       else if(some early-out is true)
       {
          Return something;
       }
       else
       {
          // BODY aka Darn I really have to do something
          Return something;
       }
    }
    

    The idea of the else-if ladder coding pattern was to return cycles back to the CPU as soon as possible, be easy to read, and avoid the kludge of nested IFs.

    Donno why this went out of style. Now I see nested IFs statements that require a 300 column wide monitor to read….. sigh

    Reply

    1. “Donno why this went out of style”

      Because it makes no sense. If you always return within an if-Block, then there’s no reason at all to continue with an else if. Just continue with normal code. It’s automatically a “else”. E.g. instead of:

      if(some early-out is true ( like someone passed me a NULL))
      {
      Return something;
      }
      else if(some early-out is true)
      {
      Return something;
      }
      

      You should always write:

      if (some early-out is true ( like someone passed me a NULL))
      {
         Return something;
      }
      
      if (some early-out is true)
      {
         Return something;
      }
      

      It’s semantically and technically identical, just with a different, much more legible, syntax.

      Reply

      1. Another reason is that this construct will prevent from (accidentally) using variables declared of the if-statement in the following else-statement.

        if (auto i = foo()) {
          // do something
        } else if (bar()) {
          // i is still in scope
        } else {
          // even here, i is in scope
        }
        
        Reply

      2. IT HAS SENSE.

        Especially if not IF() has return clause.

        The longer you will develop,
        the better you understand simple rule:
        HATE any “clear things”, which are not written in text.

        If you FORCE brain of reader to do some “calculations” to see that “hidden things”, then excuse me, you not helping to reader

        Reply

Leave a Reply