I came across this dilemma a few days ago. So, as all software engineers do, I decided to find out which approach is better because why not?

And because I did, you don't have to.

System Specifications

I am using a MacBook Pro. With the below specifications.

Specifications

Case Scenario

we have 9948 users, for which we will generate a 4-digit user PIN.
The maximum number of 4-digit PINs can be 10, 000 so while generating a random PIN, the function will be facing duplicates.

Case One: with recursion

private function generatePin(): string
    {
        $pin =  str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT);

        $exists = User::query()->where('pin', $pin)->exists();

        if ($exists) {
            return $this->generatePin();
        }

        return $pin;
    }

while running the test with the recursion function, it took approximately

  • 36 mins to complete
  • faced recursion 41102 times
with recursion

Case Two: with goto label

private function generatePinWithGoto(): string
    {
        start:

        $pin =  str_pad(rand(1, 9999), 4, '0', STR_PAD_LEFT);

        $exists = User::query()->where('pin', $pin)->exists();

        if ($exists) {
            goto start;
        }

        return $pin;
    }

While running the test with the goto label function, it took approximately

  • 36 mins to complete
  • faced recursion 53150 times
with goto label

Conclusion

To conclude this test, with more recursion faced goto label was still able to finish the process at the same time. So, it will be safe to say that the goto label is more faster than a recurring function call. As we can see from the code above it can be implemented with ease too.

I would recommend, that next time you feel a need to have a recurring function, try goto label.

Feel free to suggest other benchmarking or testing methods for more accurate results.

Update

As per the comment of one of the Reddit user SZenC

there are three issues with my comparison:

  • The database query will always take significantly more time than a recursive call or a go-to statement, so comparing the execution time of these snippets tells us next to nothing
  • The same issue exists with using randomness. Usually, that's a relatively quick call, but you can exhaust your source of randomness, in which case it again significantly impacts performance characteristics. The noise this causes will be more than any effect of recursion or goto's
  • Why no simple while-loop (or do-while-loop)? It is much more readable than a go-to and doesn't suffer from the penalties of creating a new stack frame

I understand proper benchmarking is an art of its own, but there is so much noise in your set-up, that the results aren't useful in comparing these two language features

I would like to update you that this test is nulled and does not hold any significant value. Consider this as an exercise nothing more.