The example you provided does not have any tail recursion. Consider:
(function loop(i) {
setTimeout(function main() {
alert("Hello World!");
if (i > 1) loop(i - 1);
}, 3000);
}(3));
- I've given the inner function the name
main and the outer function the name loop.
- The
loop function is immediately invoked with the value 3.
- The
loop function only does one thing. It invokes setTimeout and then returns.
- Hence, the call to
setTimeout is a tail call.
- Now,
main is called by the JavaScript event loop after 3000 milliseconds.
- When
main is called both loop and setTimeout have completed execution.
- The
main function conditionally calls loop with a decremented value of i.
- When
main calls loop, it is a tail call.
- However, it doesn't matter whether you recurse 100 times or 10000 times, the stack size will never increase so much to cause an overflow. The reason is that when you use
setTimeout, the loop function immediately returns. Hence, by the time main is called loop is no longer on the stack.
A visual explanation:
|---------------+ loop (i = 3)
|---------------+ setTimeout (main, 3000)
|
|---------------+ setTimeout return
|---------------+ loop return
~
~ 3000 milliseconds
~
|---------------+ main (i = 3)
|---------------+ alert ("Hello World!")
|
|---------------+ alert return
| i > 1 === true
|---------------+ loop (i = 2)
|---------------+ setTimeout (main, 3000)
|
|---------------+ setTimeout return
|---------------+ loop return
|---------------+ main return
~
~ 3000 milliseconds
~
|---------------+ main (i = 2)
|---------------+ alert ("Hello World!")
|
|---------------+ alert return
| i > 1 === true
|---------------+ loop (i = 1)
|---------------+ setTimeout (main, 3000)
|
|---------------+ setTimeout return
|---------------+ loop return
|---------------+ main return
~
~ 3000 milliseconds
~
|---------------+ main (i = 1)
|---------------+ alert ("Hello World!")
|
|---------------+ alert return
| i > 1 === false
|---------------+ main return
Here's what's happening:
- First,
loop(3) is called and 3000 milliseconds after it returns main is called.
- The
main function calls loop(2) and 3000 milliseconds after it returns main is called again.
- The
main function calls loop(1) and 3000 milliseconds after it returns main is called again.
Hence, the stack size never grows indefinitely because of setTimeout.
Read the following question and answer for more details:
What's the difference between a continuation and a callback?
Hope that helps.
P.S. Tail call optimization will be coming to JavaScript in ECMAScript 6 (Harmony) and it's perhaps the most awaited feature on the list.