Search code examples
javascriptrecursionv8tail-recursion

Why Does Stack Size Vary By Function (V8 Engine)?


I was just experimenting to see the max call stack size on my machine, and I discovered something very peculiar. I had the following two functions:

function fact(o) {
    if (o == 0) return 1;
    return o * fact(o - 1)
}
function k(o){
    if (o == 0) return;
    k(o - 1)
}

Calling fact(9644) is the highest number I can get to before a stack overflow. E.g., fact(9645) returns an error.

However, I can call up to k(10448) before I get a stack overflow. E.g., k(10449) returns an error.

I figured that the way in which I created my functions might affect the call stack size. So, I tried making fact a constant arrow function, and I tried changing it to a simple ternary evaluation, e.g.:

const fact = (o) => o == 0 ? 1 : o * fact(o - 1);
//OR
function fact(o) {
    return o == 0 ? 1 : o * fact(o - 1);
}

When assigning arrow functions, I also tried assigning them with let, e.g.:

let fact = (o) => o == 0 ? 1 : o * fact(o - 1);

I tried similar things with k. Results stayed the same no matter what I did.

(In case it matters, I'm running this in the Chrome Devtools console.)

Furthermore, after researching, I concluded that the V8 engine no longer does any tail recursion elimination, so I don't think this could be the issue, either. But, please, correct me on this if I'm wrong.

So, my question is, can anybody tell me why this is happening?


Solution

  • Why does stack size vary by function?

    Because different functions need to store different things on the stack. In your example, the first function fact needs to preserve the value of o across the recursive call, the second one k doesn't. No difference between normal functions and arrow functions there.

    Stack size is typically measured in bytes (because that's how memory is allocated for it), not in number of stack frames.