This describes the basics; the description is not tailored to a specific processor and kept in global terms. Your Arduino has a limited amount of RAM (2K for a 328 based board).
Your compiled code is stored as a sequence of machine instructions in flash. The processor executes those in sequence. To know what the instruction is that it needs to execute, it keeps track of it using a so-called program counter (PC). Every time that it executes an instruction, the processor increments the PC so when it is finished with the instruction, it knows what the next one is that it needs to execute.
Now what happens when your code contains an if instruction? If the condition evaluates to false, nothing changes (the PC was already incremented) and the next instruction is executed. If the condition evaluates to true, the PC will be modified so it contains the location of the first instruction of the ‘true’ block and the processor will now execute the instruction at that location.
And now your call of a function. The PC will be loaded with the location of the first instruction of your function and next the processor will execute that instruction.
So now what happens when your function is finished? The processor needs to know where it has to continue (the instruction after the call). For that, before the processor actually modifies the PC to point to the first instruction of the function, the (already incremented PC) is PUSHed into ram.
When the function is finished, the PC value that was stored in ram is read from memory and loaded in the PC (an operation know as POP) and next the processor executes the instruction that the PC points to.
So far, no issues. Now you start recursively calling a function. When the PC is pushed into memory, you will have less memory available for other things. Because you don’t return from the function, it will still be there when you call the function again from within the function and the PC is again pushed into memory. And so on and so on. In the 328 processor, the PC is two bytes, so each call you loose two bytes. Ignoring all other memory usage, after 1024 recursive calls you will run out of your 2K memory.
Heap and stack
Your RAM is ‘divided’ into two areas, the heap and the stack. Heap and stack are not physically divided; they share the same memory area in the 328 (other processor might differ). The heap starts at one side of the RAM and the stack starts at the other side; for the below we assume that the heap starts at the lower end and the stack starts at the higher end.
The variables that you declare outside any function (or not being declared with the static keyword inside a function) are placed on the heap. Dynamically allocated memory also will be placed on the heap.
So lets say that you declare two integer variables and a character array.
char hello = "Hallo Wereld";
Leaving compiler optimisations out of the equation, these are placed on the heap and 2 + 2 + 13 bytes are now occupied; we will assume that they are stored in sequence so pos occupies locations 0 and 1, posY location 2 and 3 and the character array will occupy location 3 … 15.
Now loop is called the first time (you don’t do that, it happens in another function called main()) and the PC is pushed on the stack (ram locations 2047 and 2046). loop() calls Serial.println() so two more bytes are pushed on the stack (ram locations 2045 and 2044). When Serial.println() finishes the PC is popped from the stack and the location 2045 and 2044 are free again.
Now loop() is called and the PC is pushed on the stack (locations 2045 and 2044). Because the first call to loop() never finished, you have lost the locations 2047 and 2046. Serial.println() is called again and the PC is pushed again (locations 2043 and 2042) and they will become free again when they are popped from the stack when Serial.println() finishes. Next loop() is called again and the PC is pushed on the stack and locations 2043 and 2042 are occupied; again you lost two bytes.
If you continue like this, you will find that the stack is growing towards the heap. Eventually a push of the PC will overwrite part of the character array and the Serial.println() will no longer print what it is you expect it to print (if your code does not crash at that point).
Now add another (global) variable (int posZ) after the hello variable. And in loop() you fill it with a value from Serial.read(). When you reach the point where the push is overwriting posZ and you fill posZ with a value from Serial.read(), you modify the value that was pushed on the stack. When the function returns and the PC is popped from the stack, the PC will have the incorrect value and the code at the location that the PC now points to is executed; your code is guaranteed not to do what you expect it to do. The behaviour is undefined and depends what was read.
I hope this makes clear why you in general should avoid recursive calls. Now recursive calls are often needed; in which case you need to limit the number of recursive calls so the above disasters don’t happen. Know what you’re doing and you will be fine