Hey @dandrake! This topic can be tricky without a good intro to stack usage, so check out this explanation of “tail call elimination” to learn why the current code is causing issues in the browser:
https://functional-programming-in-elm.netlify.app/recursion/tail-call-elimination.html
It outlines the rewrite technique needed in this case (pass the result list as an argument and reverse it at the end if order is important) that is common in Elm, OCaml, Haskell, Lisp, etc. The larger idea is to turn stack allocations into heap allocations, that way the size of the stack is never a problem for the code.
That resource is just a draft, but I hope it’s helpful nonetheless. And I’m sure someone on here can take it from there in terms of suggesting the specific rewrite for your case!
Aside: It would be cool to have a special error message for this case. If you have time and interest, please add an issue here with this program and the title “better guidance for stack overflows”. I imagine that it might be somewhat frail to detect the exact JS errors for stack overflows across all browsers, but I would love to show a link about tail call elimination at that exact time!
Note: JS is likely to have different stack characteristics than Haskell or OCaml. Maybe stack frames are bigger, maybe the allowed depth is smaller, etc. So the size limit may be reached at different depths in each case. (Laziness in Haskell can also end up putting more things in the heap, which is a complex tradeoff of its own. Sometimes you really want something to be allocated on the stack, thereby making GC pauses less frequent!)