ProgrammingJavascriptCareerProductivityGadgetsASP.NETWeb Design

How JavaScript optimizes tail calls

Written by
Published on
Modified on
Filed under
How JavaScript optimizes tail calls

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.

Walter Guevara is a software engineer, startup founder and currently teaches programming for a coding bootcamp. He is currently building things that don't yet exist.

Comments

No messages posted yet

Developer Poll

Q:

Stay up to date

Sign up for my FREE newsletter. Get informed of the latest happenings in the programming world.

Add a comment

Keep me up to date on the latest programming news
Add Comment

Stay up to date

Get informed of the latest happenings in the programming world.

No thanks