When you make a call (any call), the runtime allocates a new stack stack and saves the parameters and local variables of the called function in the new stack stack. When you make a recursive call, the highlighted frames contain variables with the same name, but they are stored in different frames of the stack.
To demonstrate this, I use a slightly simplified version of your example:
let rec forLoop n = if times > 0 then printf "current %d" n forLoop body (n - 1)
Now let's say that we call forLoop 2 from some top-level function or program module. Runtime allocates a stack for the call and stores the parameter value in a frame representing the forLoop call:
+----------------------+ | forLoop with n = 2 | +----------------------+ | program | +----------------------+
The forLoop function prints 2 and continues to work. It makes a recursive call to forLoop 1 , which allocates a new stack stack:
+----------------------+ | forLoop with n = 1 | +----------------------+ | forLoop with n = 2 | +----------------------+ | program | +----------------------+
Since 1 > 0 program enters the then branch again, prints 1 and makes another recursive call to the forLoop function:
+----------------------+ | forLoop with n = 0 | +----------------------+ | forLoop with n = 1 | +----------------------+ | forLoop with n = 2 | +----------------------+ | program | +----------------------+
At this point, the forLoop function returns without any other calls, and the frame stacks are deleted one after another when programs return from all recursive calls. As you can see from the diagrams, we created three different variables that were stored in different frames of the stack (but they were all named n ).
It is also worth noting that the F # compiler performs various optimizations, such as tail-call, which can replace the call and allocation of a new stack frame using a mutable variable (which is more efficient). However, this is just an optimization, and you don't need to worry about it if you want to understand the mental recursion model.