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.