« Back to Index

JS Tail Call Optimisation

View original Gist on GitHub

1. TCO description.md

The problem

If a function calls itself recursively then the JavaScript engine has to create a new ‘stack’ (i.e. allocate a chunk of memory) to keep track of the function’s arguments.

Let’s look at an example of this happening:

function sum(x, y) {
    if (y > 0) {
      return sum(x + 1, y - 1);
    } else {
      return x;
    }
}

sum(1, 10); // => 11

In the above code, the JavaScript engine has to create a new stack for each recursive call.

This happens because the original call to the sum function (i.e. sum(1, 10)) doesn’t complete until the very last call to sum returns the value of x. Then x is returned to the caller and that caller returns x to its caller, all the way back to the very first call to sum where by it returns the result of the line return sum(x + 1, y - 1); (which ultimately was the value of x passed along from the last call to sum).

If we create a stack deep enough (e.g. sum(1, 100000)) then the JavaScript engine will throw a RangeError: Maximum call stack size exceeded.

The solution

The fix to this problem is to use fewer stacks.

To achieve this we could rewrite the code to not be recursive; so in other words: use a loop!

The problem with using a loop is that it’s not as elegant (or clean/easy to read) as the recursive style of functional programming.

Another solution is to use a type of functional pattern called “trampolining”. Let’s take a look at one implementation of it…

Example solution

function tco(f) {
    var value;
    var active = false;
    var accumulated = [];

    return function accumulator() {
        accumulated.push(arguments);

        if (!active) {
            active = true;

            while (accumulated.length) {
                value = f.apply(this, accumulated.shift());
            }

            active = false;

            return value;
        }
    }
}

var sum = tco(function(x, y) {
    if (y > 0) {
      return sum(x + 1, y - 1)
    }
    else {
      return x
    }
});

sum(1, 100000) // => 100001

Here we’ve written a tco function which wraps around the same code we had previously.

Explanation

This could take some time to wrap your head around (lord knows it took me long enough), so if you don’t get it the first time around then it’s probably best to execute the above code via your browsers developer tools and use a debugger statement to step through the code whilst reading this explanation…

Note: the above code is an abstraction I found here: https://gist.github.com/Gozala/1697037. It makes tail call optimising any function really easy.

The secret sauce to this entire chunk of code is coming up!

Alternative implementations?

There is another implementation I’ve seen which is much simpler to understand but not necessarily as easy to abstract like the tco method above.

Let’s take a look at the code first (note: my explanation assumes an understanding of the this keyword and changing its context, if you’re unsure then read more about it here):

function trampoline(f) {
    while (f && f instanceof Function) {
        f = f();
    }
    return f;
}

function sum(x, y) {
    function recur(x, y) {
        if (y > 0) {
          return recur.bind(null, x + 1, y - 1);
        } else {
          return x;
        }
    }

    return trampoline(recur.bind(null, x, y));
}

sum(1, 10); // => 11

It breaks down like this…

2. Clojure explanation.md

The following is copied directly from: http://aphyr.com/tags/Clojure-from-the-ground-up and it provides an alternative explanation of the problem/solution. I’ve commented the Clojure code heavily for those unfamiliar with it.

Every time you call a function, the arguments for that function are stored in memory, in a region called the stack. They remain there for as long as the function is being called (including any deeper function calls).

Below is a Clojure example (both commented and non-commented version):

; define a multi-arity function (different code branches depending on args provided when called)
(defn sum
  ; arity branch 1
  ; accepts a single argument: numbers
  ([numbers]
   ; the following expression/form is the body of the function
   ; here we re-call the sum function again but provide two arguments (so we will reach the 2nd branch)
   ; this is a defensive way to ensure a function is called correctly
   (sum 0 numbers))
  ; arity branch 2
  ; accepts two arguments: subtotal and numbers
  ; the subtotal argument acts as an 'accumulator'
  ([subtotal numbers]
   ; the following expression/form is the body of the function
   ; we define an if condition (a special kind of conditional that does two things)
   ; if the expression (first numbers) returns something truthy then store that in the n let
   ; so we can use it within the if statement (otherwise the value of n is nil)
   (if-let [n (first numbers)]
     ; if condition is truthy...
     ; recursively call this current function using the recur function
     ; we pass in the result of two sub expressions (+ subtotal n) and (rest numbers)
     (recur (+ subtotal n) (rest numbers))
     ; if condition is falsey...
     subtotal)))
(defn sum
  ([numbers]
   (sum 0 numbers))
  ([subtotal numbers]
   (if-let [n (first numbers)]
     (recur (+ subtotal n) (rest numbers))
     subtotal)))

The result of which is…

user=> (sum (range 100000))
4999950000

We’ve added an additional parameter to the function. In its two-argument form, sum now takes an accumulator, subtotal, which represents the count so far. In addition, recur has taken the place of sum.

Notice, however, that the final expression to be evaluated is not +, but sum (viz recur) itself. We don’t need to hang on to any of the variables in this function any more, because the final return value won’t depend on them.

recur hints to the Clojure compiler that we don’t need to hold on to the stack, and can re-use that space for other things. This is called a tail-recursive function, and it requires only a single stack frame no matter how deep the recursive calls go.