Here the function just checks if its input is 1. In this case, the constant value of 1 is returned. Otherwise it returns the exact definition of the factorial, which says that f(x) is equal to x * f(x - 1). The elegance of Scala is exactly in the ability to connect math and programming. Note that the f() function declares its return type as Int. That's not surprising on its own, but deserve noting, because usually Scala can guess the return type of a function on its own, and Scala programmers are all but apt to press one key stroke more than required. So why the return type here? Because recursive functions are explicitly required to declare the return type, to allow the compiler to perform its type checking.
now 4 is different from 1 and the call evaluates to 4 * f(4 - 1). But the value of the right part of the product can be determined only by calling f(4 - 1) and later multiplying it by 4. The context of f(4) must be left in the stack and a new for f(4 - 1) must be created. Then the situation repeats and f(3) is 3 * f(3 - 1) which must be evaluated after calling f(3 - 1). f(4) so creates 4 contexts in the stack, one per each call. What would happen then when f() is called with 50000? Quite predictably it throws a java.lang.StackOverflowError exception. How can we solve this?
Tail recursion dues its name to the fact that the recursive call happens as the last action of the calling function. Remember: the last action, like the tail is the last part of an animal. And by using the @tailrec annotation we ensure that tail recursion has be detected by the compiler which otherwise would abort compilation with an error. Now we're done!