More articles

How JavaScript optimizes tail calls

Written by
Published on

A commonly seen useful shorthand when writing code is the ability to call a function as a return statement. For example:

function getFirstName()
{
	let name = 'Bob';
	return cleanName(name);
}

In ES5, that type of function call would be handled like any other function call. A new stack frame is created and pushed onto the call stack to represent the function call. This means that every previous stack frame is still kept in memory. This can cause issues if the call stack gets too large.

How ES6 handles tail calls

ES6 takes a slightly different approach, if you are working in strict mode. If you are not in strict mode, then tail calls are left untouched. But if you are, then instead of creating a new stack frame for the tail call, the current stack frame will be cleared and reused, but only under the following conditions:

1. The tail call function does not require access to variables in the current stack frame.
2. The function making the tail call has no further work to do after the tail call return statement.
3. The result of the tail call is returned as the function value.

Note that the example above would not meet this criteria, because the cleanName() call uses a local variable, violating the first rule mentioned above. But the following example would work just fine:

"use strict";

function functionCaller(name)
{
    if (name == 'helloWorld')
	return helloWorld();
}

This function meets the criteria and would be optimized by the JavaScript engine.

When it matters

function factorial(n)
    {
        if (n <= 1)
        {
            return 1;
        } 
        else
        {
            // not optimized - must multiply after returning return n * factorial(n - 1);
        }
    }

In this particular example, the ES6 would not optimize the return call, since it does not technically complete the operation. Working with a small number, you might not notice the different in performance.

However if you were calculating the factorial of a massive value, then performance could definitely play a large role in your code.

The following is an updated version of the same algorithm from above. In this new version, we move the multiplication to outside of the return statement and use a secondary parameter to carry over the multiplication instead.


    function factorial(n, p = 1)
    {
        if (n <= 1) {
            return 1 * p;
        }
    
        else {
		    let result = n * p; // optimized
		    return factorial(n - 1, result);
	    }
    }

Just a quick example to show how JavaScript handles your code on the lower levels and how sometimes the way that you code can indeed have varying results on performance.

Land your next big coding job. Search through 1000's of job listings.
X

Discussion / Comments / Questions

No messages posted yet

Add a comment

Send me your weekly newsletter filled with awesome ideas
Post comment