Tail-call optimization in Elm

What is TCO?

Tail-call optimization (TCO) is a very neat trick that the Elm compiler does to make recursive functions a lot more performant and stackoverflow-proof.

Evan Czaplicki describes it very well in this article and I recommend you go read it. He calls it tail-call elimination but it’s a different name for the same thing.

To summarize Evan’s article, a “tail-call optimized” function is a recursive function that gets compiled to using a loop instead of function calls to itself. Let’s take the following code as an example…

Read in full here:

This thread was posted by one of our members via one of our news source trackers.


Corresponding tweet for this thread:

Share link for this tweet.

1 Like

That’s not TCO that Elm does, it’s self-call tail recursive optimization, it only can happen in fully known contexts and is not general enough to be full TCO. Full Tail Call Optimization is where a call is in the tail-most position of a function, that means you can then compile essentially a goto to it (or in the Rust proposal a become for example), this means the call can be dynamically made, can be any amount deep through any amount of function stacks, etc… etc… I really don’t like how the Elm community keeps trying to misrepresent things about the Elm language, it’s an annoying recurring pattern…

In fact, let’s try it, I go to: Try Elm!
And I try running:

import Html exposing (text)

entry f i acc = if i <= 0 then acc else f (i - 1) (acc + i)

dispatch1 i acc = entry dispatch2 i acc

dispatch2 i acc = entry dispatch1 i acc

main =
  let i = dispatch1 10000 0 in
  text (String.fromInt i)

And the result is:

Initialization Error

InternalError: too much recursion

Well, let’s try this in Elixir, a language that DOES support TCO, so the same code ported running through the repl:

❯ iex
Erlang/OTP 24 [RELEASE CANDIDATE 3] [erts-12.0] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit]

Interactive Elixir (1.12.0-rc.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule TestingTCO do
...(1)>   def entry(f, i, acc), do: if(i <= 0, do: acc, else: f.(i-1, acc+i))
...(1)>   def dispatch1(i, acc), do: entry(&dispatch2/2, i, acc)
...(1)>   def dispatch2(i, acc), do: entry(&dispatch1/2, i, acc)
...(1)> end
{:module, TestingTCO,
 <<70, 79, 82, 49, 0, 0, 7, 176, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 202,
   0, 0, 0, 20, 17, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 105,
   110, 103, 84, 67, 79, 8, 95, 95, 105, 110, ...>>, {:dispatch2, 2}}
iex(2)> TestingTCO.dispatch1(10000, 0)

And since Elixir really does implement TCO and not just a simple recursive loop optimization unlike elm’s communities repeating lies then we can go way way higher!

iex(3)> TestingTCO.dispatch1(1000000, 0)

Elm has a lot of issues both as a language and as a community, and passing off a very very common optimization pass performed in almost every language as TCO is just the tip of the iceberg…


Even if it’s not true TCO, performance enhancements are always welcome when it comes to JS land :nerd_face:


It doesn’t actually change performance all that much if any. ^.^;

TCO and self-recursion optimization isn’t a performance optimization, it’s a do-not-blow-the-stack optimization.

They are still very different though, and self-recursion optimization is not TCO, not in comprehension, not in how they act, not in any form. TCO is insanely powerful and opens up a whole world of really interesting things and programming patterns. Self-recursion optimization does not, it just helps fix-up a single localized algorithm.

With proper TCO you can implement greenthreads on it, like Rust’s async, without any weird state system magic. ^.^

1 Like

I have already read a lot about the disadvantages of Elm. In your opinion, what is currently better than Elm (in terms of the quality of code generation, what to choose for creating a new application) for the front end part?


Well I’m biased toward Rust in WASM myself so that would be first, lol, but if you want something more traditional than typescript or purescript are both good. ReasonML is fantastic especially if you’ll be doing much react.


@OvermindDL1, thank you for sharing your opinion. Interesting. ReasonML has not yet looked (a quick glance at the description - I see positive reviews. Implemented on the basis of OCalm).

Over the years, I also had a lot of work with Rust (I translated a book (Rust book) for the Russian-speaking community, write additional source code materials for the articles, wrote a course for beginners and much more, but now I am interested in Erlang.
I also did a research project (studied Rust-compiler error indexes through testing).


I work a lot with TypeScript and Erlang. Elm seemed to me an interesting solution (somewhat similar to Erlang). He has a large community, too. Books. Conferences. I like ideas of that topic “What is success?”

Unfortunately, until you work, you will not get bumps in the chosen technology, you will not be completely sure whether it is suitable for your work or not.


ReasonML is just a PP (preprocessor) for OCaml. You can generate normal OCaml programs with it, or plug in other OCaml backends, like say JSOO or bucklescript (or whatever its called now) to generate javascript for example. ReasonML comes with a very node-centric array of ecosystem tools to fit in to that all though, quite easy to pick up.

That community has a lot of churn though. In general there’s a small set of dedicated people who stick around, but a few select some of them are rather hostile to new users, which tends to drive them away.

As for the language, it has a lot of design issues, many of which have repeatedly caused miscompilations over time, and that’s even before getting to the elm architecture style of doing things (which is just a standard centralized event processor), which is overall fine, but rather difficult to work with, especially when combining with other things for a variety of reasons. I tried to use it in earnest for over a year, but a combination of language misdesigns and the hostile community were, not pleasant… >.>

Even things like the OP article just outright lying (there is no TCO in elm) has been a common pattern over time.


That’s a great point - you never know your experience may be different to ODL’s or someone else’s …so if you do try it, let us know how you get on :+1:

I’m definitely interested in WASM (and part of the reason I am interested in Rust) though I wonder whether languages like #assemblyscript might be easier/more accessible? Have you looked at it ODL? Any thoughts on it? :smiley:


Yep, it’s just a restricted form of typescript, it’s definitely useful for typescript develops to get more speed out of making minor changes to their existing code. It doesn’t get you the much larger speed boosts that a high optimizing compiler like rust can do (not that ever project needs it, but its nice to be battery friendly), plus things like sharing the same code between your front-end and back-end rust webserver can be a very nice boon too. ^.^