The important thing is to maintain a clear structure.
A simple switch/case statement (or a series of non-nested if statements) that contains a return within each and every case, will be perfectly clear. That is, my first preference would be for early returns. Think of these early returns as being rather like a
But if you have a mildly complex nest of logic, where returns may have to be scattered all over the place at different levels, then I would move to using an intermediate variable and a one-return-only approach.
To my eye, this approach always seems messier at first glance, but it serves to reimpose structured control flow - since we must now incorporate structured logic that ensures you can get to the bottom of the function without executing anything else in between (the parts that would have been jumped out-of and over, if the function had returned early).
If you've decided that early returns are no good, but it also becomes too difficult or unreasonably messy to implement the single-return approach, because of the difficulty of getting to the bottom of the function (without using
GOTOs), that is a sure sign that the entire thing is inherently poorly structured and needs to be broken down further into separate methods.
Once we have broken down excessively complex logic into separate methods, we may be able to safely go back to using early returns in each.
If you look at early-returns like this - that they are akin to
GOTO statements - you want to ask yourself how many levels of (and what complexity of) structured code are being cut-across by their use. If you're cutting across one level (e.g. exiting from a loop or a switch which is the outer statement of the method, with the inner scope containing one or two lines), that is acceptable.
Maybe it is acceptable even if there are a couple of nested loops or switches, if the logic inside the outer structure is trivial - for example, if one loop is nested inside the other to traverse a single two-dimensional array, or a nested switch that simply evaluates two variables, before getting to the inner scope containing one or two statements per case (or maybe just the return statement itself).
But if you're cutting-across control-flow structure that is several levels deep, containing all sorts of non-trivial logic at each level, that is when returning early may be seen as the flouting of structure. The behaviour of unstructured code is far more difficult to reason about and verify, and even if written correctly in the first place, becomes very difficult to re-grasp or modify in future (even for the person who wrote it, but especially for others).
As a final note, if the function has a common "tail" of code, such as might be required for logging, sanity checks, or cleanup code, or as a convenient place to set a debugger breakpoint - or if you might want the facility to add these in future, without radically reworking code that is already tested in use - then early returns are precluded anyway, and you'll have to use intermediate variables and a single return as a starting point. Developers who work in an environment where these requirements are usual, may simply use intermediate variables habitually.