# Elm Stack Saver

**URL:** <https://discourse.elm-lang.org/t/elm-stack-saver/10512>\
**Category:** Show and Tell\
**Created:** [December 29, 2025, 11:37am UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512 "2025-12-29T11:37:59Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [December 29, 2025, 11:37am UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/1 "2025-12-29T11:37:59Z")

</div>

This might come in handy if you are having issues with stack overflows. It tries to detect all recursion which is not tail optimized. It will detect individual recursive functions as well as mutually recursive cycles of functions, and also functions defined inside let..in blocks.

There are some more ‘dynamic’ situations it can miss, such as if recursion happens within runtime created closures.

It works by scanning an elm.js compiler output and building the call graph from that, then applying Tarjan’s algorithm to detect cycles. The JS function names are converted back into Elm formatted names, unless you specified the `--jsnames` CLI option.

> **[GitHub - the-sett/elm-stack-saver: Elm Stack Recusion Detector](https://github.com/the-sett/elm-stack-saver)**
>
> Elm Stack Recusion Detector

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [December 29, 2025, 5:11pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/2 "2025-12-29T17:11:03Z")

</div>

And after that… it turns out that my stack overflow bug is actually the same as this one, which is an elm compiler error, and does not show up as a loop in my stack saver scan!

> <https://github.com/elm/compiler/issues/2268#issuecomment-1671931317>
>
> The code generation for the tail call optimization does not appear to create pro…per closures to variables. In the following example I would expect \`goodOutput\` and \`badOutput\` to be the same, but instead all the closures generated in \`tcoMakeLazy\` reference the same variable which contains its final value of 3. 
> 
> \## SSCCE
> 
> \`\`\`elm
> makeLazy : List a -\> List (() -\> a) -\> List (() -\> a)
> makeLazy list accum =
> case list of
> item :: items -\>
> makeLazy items \<| ((\\\_ -\> item) :: accum)
> 
> \_ -\>
> accum
> 
> tcoMakeLazy : List a -\> List (() -\> a) -\> List (() -\> a)
> tcoMakeLazy list accum =
> case list of
> item :: items -\>
> tcoMakeLazy items ((\\\_ -\> item) :: accum)
> 
> \_ -\>
> accum
> 
> \-- \[1, 2, 3\]
> goodOutput =
> makeLazy \[1, 2, 3 \] \[\] |\> List.map (\\f -\> f ())
> 
> \-- \[3, 3, 3\]
> badOutput =
> tcoMakeLazy \[1, 2, 3 \] \[\] |\> List.map (\\f -\> f ())
> \`\`\`
> 
> \- \*\*Elm:\*\* 0.19
> \- \*\*Browser:\*\* Any
> \- \*\*Operating System:\*\* Any

Now I wonder if I could pick this up from scanning the javascript and looking for the mutated variable that causes 🤔

---

<div class="post-metadata">

**Author:** ![jxxcarlson](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jxxcarlson/32/866_2.png) [@jxxcarlson](https://discourse.elm-lang.org/u/jxxcarlson)\
**Post date:** [December 29, 2025, 5:34pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/3 "2025-12-29T17:34:15Z")

</div>

Great contribution! I am going scan my scripta compiler with this. Haven’t experienced any stack overflows but that doesn’t prove anything.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [December 29, 2025, 5:42pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/4 "2025-12-29T17:42:07Z")

</div>

I have no real idea about the best way to distribute python programs. This one has a few dependencies:

```auto
import argparse
import re
import sys
from collections import defaultdict

```

Which must exist on my system already, since I can run it with `python3 ess` without explicitly setting up the dependencies. But it maybe needs a proper way of installing/sharing it in the world of python - or rewrite it in something else.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [December 29, 2025, 6:04pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/5 "2025-12-29T18:04:01Z")

</div>

I just pushed an update that adds TCO closure capture bug detection.

---

<div class="post-metadata">

**Author:** ![jfmengels](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jfmengels/32/3124_2.png) [@jfmengels](https://discourse.elm-lang.org/u/jfmengels)\
**Post date:** [December 29, 2025, 6:28pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/6 "2025-12-29T18:28:59Z")

</div>

When hitting these stack overflow errors (or before hitting them), the `elm-review` rule [`NoUnoptimizedRecursion`](https://package.elm-lang.org/packages/jfmengels/elm-review-performance/latest/NoUnoptimizedRecursion) might also be helpful/relevant.

But being able to catch these across multiple declarations AND in packages can be really useful. I’ll keep this in my toolbox, thanks!

I’m thinking it could be nice to present these cycles not by cycle count (not sure that’s very interesting) but rather by origin. Is this a recursion loop that comes from a package (and if so which one), or one from the project’s code?  
I’m thinking there could be a section for each package, starting (or ending, depending what you think is going to look most relevant to the user) with the local code.

---

<div class="post-metadata">

**Author:** ![lydell](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/lydell/32/178_2.png) [@lydell](https://discourse.elm-lang.org/u/lydell)\
**Post date:** [December 29, 2025, 7:30pm UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/7 "2025-12-29T19:30:02Z")

</div>

`argparse`, `re` and `sys` are in the [standard library](https://docs.python.org/3/library/index.html), which ships with Python itself.

The hyped `uv` tool allows [declaring external script dependencies in a comment at the top of the file](https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies). That seems like a good fit for a little script like yours.

(I used to be a bit into Python 5+ years ago and still follow the Python world a bit, but I haven’t tried `uv` myself, just read and heard about it.)

---

<div class="post-metadata">

**Author:** ![dta](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dta/32/1917_2.png) [@dta](https://discourse.elm-lang.org/u/dta)\
**Post date:** [December 30, 2025, 12:16am UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/8 "2025-12-30T00:16:33Z")

</div>

`collections` is also in the standard library

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [December 30, 2025, 8:35am UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/9 "2025-12-30T08:35:18Z")

</div>

The tail-call closure capture bug is fixed in Zokka I think ? Just thinking, if there is going to be an Elm 0.19.2 to address some of the recent CI issues, it might be worth trying to get that fix, or equivalent, in that release too? You will likely run into this bug if doing continuation passing style with TCO.

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [January 9, 2026, 8:36am UTC](https://discourse.elm-lang.org/t/elm-stack-saver/10512/10 "2026-01-09T08:36:01Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
