One place this may be coming from not covered by any of the existing answers is functional programming. Many functional languages enforce single-exit at the language level: for instance, apparently OCaml, Clojure, Erlang and F# do not even have a return statement, while Haskell’s return is a function, not a control flow operator, and doesn’t allow multiple exit. Scala, which also supports a more imperative style, has a return statement which does affect control flow... but best practice on the functional side of things is to never, ever use it so we’re right back at single exit.
So why are multiple returns a bad thing in functional programming?
Return as an expression
Functional languages are expression-oriented and generally want everything that can to be an expression, meaning that it evaluates to some value. (Which, incidentally, is how they can manage not having return statements at all - the function body is an expression, so has a value, and then the function evaluates to that value. No explicit return needed.)
Functional languages also want their expressions to be referentially transparent. What this means is that you can swap out the expression for its value without changing the behaviour of the program. This means that you can extract parts of methods, inline them, save intermediate results in variables, etc. etc. and know that this refactoring cannot possibly break anything.
But if we try to treat return
as an expression, things start getting weird. Looking at Scala as an example because it's what I'm most familiar with and it is a functional language allowing returns...
def f1 : Int = {
if (condition)
return 42
97
}
// should have the same behaviour under referential transparency
// but in practice f2 will always evaluate to 97
def extractedReturn : Int = return 42
def f2: Int = {
if (condition)
extractedReturn
97
}
// wait, what happens if we lazy-evaluate the return expression outside the method it's in?
def lazyReturn : () => Int = () => return () => 42
def f3: Int = {
if (condition)
lazyReturn()
97
}
(Examples inspired from tpolecat's post linked above; the last one actually throws an exception.)
The problem is that an explicit return has side effects - it doesn't just evaluate to a value, it also changes the control flow of the program. And that causes lack of referential transparency, as well as the weird edge cases involving lazy execution.
You could argue that return should be treated as a special case and not held to the functional standards, similar to how some functional languages support throwing exceptions. This is the path Scala took. But there's still a real cost to actually using the return statement, because it's so strongly against the language paradigm. And there's not much benefit, because...
Alternate functional idioms
For pretty much all of the use cases where multiple return statements are useful in more imperative code, there are functional idioms that handle the same situation without needing an early exit point.
Take the if/else example in the question. In functional languages, if
isn't a control flow statement, it's an expression with a value, behaving pretty much exactly like the ternary operator:
def singleExitIf(condition: Boolean) : Int =
if (condition)
42
else
97
More complex if/else if/else if/... chains can often be handled well by pattern matching, which functional languages typically have strong support for.
Guard clauses prior to some lengthy computation are a natural fit for monads. In the functional paradigm you're generally either already working in or can easily lift yourself into the context of some monad which has something like a "fail-fast" execution path (the None type in Maybe
/Option
, Left in right-biased Either
, errors in IO
, etc.) So if you want to abort computation early subject to some condition, instead of returning early you switch into that path:
def doComplexCalculation(person: Person) : Int =
Some(person)
.filter(_.isAlive) // if the person is dead this will evaluate to None
.map { livingPerson =>
// long complex calculations requiring the person to be alive go here
}
.getOrElse(deadPersonFallbackValue)
How do you break out of a loop early? Well, you almost certainly don't want to be using a loop in the first place. Maybe you want some collection-level operation - some of these terminate traversal early based on a condition, like find
or takeWhile
. Or maybe you need a tail recursive function where you can stop the recursion at the right point.
In short, if you think you need an early return in a functional language, there's almost invariably some more idiomatic way to express it that doesn't require multiple exit points.
Conclusion
In functional languages, early returns violate the functional paradigm and also don't offer the same benefits they do in more imperative/OOP ones. Most functional languages don't allow them at all, and in the one that does they're viewed as bad practice. Programmers coming from the functional side of things may extend that to more imperative/OOP languages - especially because once you're used to multiple exit points not being a possibility, code with early returns becomes significantly harder to read and understand and it's easy to conclude they must be bad style.