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

16 Comments

  1. Jens

    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. Jens

      I find the last quoted block especially scary.

      Reply
  2. Amit

    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. Arne Mertz

      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
  3. Michał Zubrzycki

    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
  4. Kerry Thompson

    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
  5. Jeroen

    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

  6. 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
  7. Hamza Ahmed Zia

    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
  8. Chetan

    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
  9. mike

    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. Jan

      “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. Anders Dalvander

        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

Your email address will not be published. Required fields are marked *