When this matters
Tail calls matter when a loop is naturally written as recursion. If the recursive call is the final action, Lispex can replace the current continuation instead of stacking another one.
See it run
(let loop ((n 10000) (acc 0)) (if (= n 0) acc (loop (- n 1) (+ acc 1))))Observed result
10000Read the example
Named let creates loop. Each branch either returns acc or immediately calls loop with smaller n and updated acc; there is no pending addition after the call. Ten thousand iterations therefore finish as one logical loop.
How to reason about it
- Tail positions include final bodies, both
ifbranches, normalizedcond/case, finaland/or,do,call-with-values, andapply. - Proper tail behavior concerns continuation growth, not equal wall-clock time or unlimited execution.
- Resource limits remain deterministic and profile-specific even for tail-recursive programs.
Choose quickly
| Shape | Tail call? | Why |
|---|---|---|
(if done? answer (loop next)) | yes | the selected branch result is returned directly |
(begin (display x) (loop next)) | yes | the call is the final form |
(+ x (loop next)) | no | addition remains after the recursive result |
(map loop items) | no for the callback | map still owns the callback result |
(apply loop args) in tail position | yes | apply preserves the caller’s tail position |
A common mistake
Non-tail recursion may reach the declared resource limit.
Current boundaries
- A clean resource fault is not evidence that native and WASM have the same threshold.
Keep going
The recursion guide shows base cases and accumulator conversion. Determinism and Resources explains why tail-safe does not mean unbounded.