{0 Tail call optimization}

Tail call optimization (TCO) support in JavaScript depends on the VM. JavaScriptCore
(Safari, Bun) implements TCO, but V8 (Chrome, Node.js) and SpiderMonkey (Firefox)
do not. To ensure portable stack safety, js_of_ocaml optimizes common tail call patterns.

{1:summary What gets optimized}

{t
 | Pattern | Optimization |
 | Self-recursive tail calls | Compiled to a loop |
 | Mutually recursive tail calls | Compiled with a trampoline |
 | Other tail calls | Not optimized (may overflow) |
}

To optimize other tail calls, try [--effects=cps] or [--effects=double-translation].
This partially transforms the code to continuation-passing style, but has a performance cost.
See {{!page-"effects"}effect handlers}.

{1:self-recursive Self-recursive functions}

Self-recursive tail calls are compiled into loops:

{@ocaml[
let rec fact x acc =
  if x = 0
  then acc
  else fact (pred x) (acc * x)
]}

Generated JavaScript:
{@javascript[
function fact(x$1, acc$1){
  var x = x$1, acc = acc$1;
  for(;;){
    if(0 === x) return acc;
    var acc$0 = runtime.caml_mul(acc, x), x$0 = x - 1 | 0;
    x = x$0;
    acc = acc$0;
  }
}
]}

{1:mutual-recursive Mutually recursive functions}

Mutually recursive functions use a trampoline—a loop that catches returned
continuations and re-invokes them, avoiding deep call stacks:

{@ocaml[
let rec even n =
  match n with
  | 0 -> true
  | x -> odd (x - 1)
and odd n =
  match n with
  | 0 -> false
  | x -> even (x - 1)
]}

Generated JavaScript:
{@javascript[
function even$0(counter, n){
  if(0 === n) return 1;
  var a = n - 1 | 0;
  if(counter >= 50) return caml_trampoline_return(odd$0, [0, a]);
  var counter$0 = counter + 1 | 0;
  return odd$0(counter$0, a);
}
function even(n){return caml_trampoline(even$0(0, n));}
function odd$0(counter, n){
  if(0 === n) return 0;
  var a = n - 1 | 0;
  if(counter >= 50) return caml_trampoline_return(even$0, [0, a]);
  var counter$0 = counter + 1 | 0;
  return even$0(counter$0, a);
}
function odd(n){return caml_trampoline(odd$0(0, n));}
]}

{b Note}: The generated code doesn't return to the trampoline on every call (controlled
by the [tc_depth] parameter, default 50). This balances stack safety with performance.

{1:not-optimized Patterns not optimized}

These patterns are {b not} optimized and may cause stack overflows with deep recursion:

{b Tail call through a local function:}
{@ocaml[
let rec f x =
  let g delta = f (x - delta) in
  if x < 0 then 0
  else if x mod 2 = 0 then g 2
  else g 1
]}

{b Tail call to a function argument:}
{@ocaml[
let bind x f =
  match x with
  | None -> None
  | Some x -> f x  (* f is unknown, can't optimize *)
]}

For these patterns, you can try [--effects=cps].

{1:tuning Tuning}

The trampoline depth can be adjusted with [--set tc_depth=N] (default: 50).
Higher values improve performance but increase stack usage.

{1 See also}

- {{!page-"effects"}Effect handlers} — Full tail call optimization with CPS
- {{!page-"options"}Command-line options} — The [tc_depth] parameter
