Chapter 4. Algorithms and Flow Control
The overall structure of your code is one of the main determinants as to how fast it will execute. Having a very small amount of code doesn’t necessarily mean that it will run quickly, and having a large amount of code doesn’t necessarily mean that it will run slowly. A lot of the performance impact is directly related to how the code has been organized and how you’re attempting to solve a given problem.
The techniques in this chapter aren’t necessarily unique to JavaScript and are often taught as performance optimizations for other languages. There are some deviations from advice given for other languages, though, as there are many more JavaScript engines to deal with and their quirks need to be considered, but all of the techniques are based on prevailing computer science knowledge.
Loops
In most programming languages, the majority of code execution time is spent within loops. Looping over a series of values is one of the most frequently used patterns in programming and as such is also one of the areas where efforts to improve performance must be focused. Understanding the performance impact of loops in JavaScript is especially important, as infinite or long-running loops severely impact the overall user experience.
Types of Loops
ECMA-262, 3rd Edition, the specification that defines
JavaScript’s basic syntax and behavior, defines four types of loops.
The first is the standard for
loop,
which shares its syntax with other C-like languages:
for (var i=0; i < 10; i++){ //loop body }
The for
loop tends to be the
most commonly used JavaScript looping construct. There are four parts
to the for
loop: initialization,
pretest condition, post-execute, and the loop body. When a for
loop is encountered, the initialization
code is executed first, followed by the pretest condition. If the pretest condition evaluates to true
, then the body of the loop is executed.
After the body is executed, the post-execute code is run. The
perceived encapsulation of the for
loop makes it a favorite of developers.
Note
Note that placing a var
statement in the initialization part of a for
loop creates a function-level
variable, not a loop-level one. JavaScript has only function-level
scope, and so defining a new variable inside of a for
loop is the same as defining a new
variable outside of the loop.
The second type of loop is the while
loop. A while
loop is a
simple pretest loop comprised of a pretest condition and a loop
body:
var i = 0; while(i < 10){ //loop body i++; }
Before the loop body is executed, the pretest condition is
evaluated. If the condition evaluates to true
, then the loop body is executed;
otherwise, the loop body is skipped. Any for
loop can also be written as a while
loop and vice versa.
The third type of loop is the do-while
loop. A do-while
loop is
the only post-test loop available in JavaScript and is made up of two
parts, the loop body and the post-test condition:
var i = 0; do { //loop body } while (i++ < 10);
In a do-while
loop, the loop
body is always executed at least once, and the post-test condition
determines whether the loop should be executed again.
The fourth and last loop is the for-in
loop. This loop has a very special purpose: it enumerates the
named properties of any object. The basic format is as follows:
for (var prop in object){ //loop body }
Each time the loop is executed, the prop
variable is filled with the name of another property (a string)
that exists on the object
until all
properties have been returned. The returned properties are both those
that exist on the object instance and those inherited through its
prototype chain.
Loop Performance
A constant source of debate regarding loop performance
is which loop to use. Of the four loop types provided by JavaScript,
only one of them is significantly slower than the others: the for-in
loop.
Since each iteration through the loop results in a property
lookup either on the instance or on a prototype, the for-in
loop has considerably more overhead
per iteration and is therefore slower than the other loops. For the
same number of loop iterations, a for-in
loop can end up as much as seven
times slower than the other loop types. For this reason, it’s
recommended to avoid the for-in
loop unless your intent is to iterate over an unknown number of object
properties. If you have a finite, known list of properties to iterate
over, it is faster to use one of the other loop types and use a
pattern such as this:
var props = ["prop1", "prop2"], i = 0; while (i < props.length){ process(object[props[i++]]); }
This code creates an array whose members are property names. The
while
loop is used to iterate over this small number of properties
and process the appropriate member on object
. Rather than looking up each and
every property on object
, the code
focuses on only the properties of interest, saving loop overhead and
time.
Aside from the for-in
loop,
all other loop types have equivalent performance characteristics such
that it’s not useful to try to determine which is fastest. The choice
of loop type should be based on your requirements rather than
performance concerns.
If loop type doesn’t contribute to loop performance, then what does? There are actually just two factors:
Work done per iteration
Number of iterations
By decreasing either or both of these, you can positively impact the overall performance of the loop.
Decreasing the work per iteration
It stands to reason that if a single pass through a loop takes a long time to execute, then multiple passes through the loop will take even longer. Limiting the number of expensive operations done in the loop body is a good way to speed up the entire loop.
A typical array-processing loop can be created using any of the three faster loop types. The code is most frequently written as follows:
//original loops for (var i=0; i < items.length; i++){ process(items[i]); } var j=0; while (j < items.length){ process(items[j++]); } var k=0; do { process(items[k++]); } while (k < items.length);
In each of these loops, there are several operations happening each time the loop body is executed:
One property lookup (
items.length
) in the control conditionOne comparison (
i < items.length
) in the control conditionOne comparison to see whether the control condition evaluates to
true
(i < items.length == true
)One increment operation (
i++
)One array lookup (
items[i]
)One function call (
process(items[i])
)
There’s a lot going on per iteration of these simple loops,
even though there’s not much code. The speed at which the code will
execute is largely determined by what process()
does to each item, but
even so, reducing the total number of operations per iteration can
greatly improve the overall loop performance.
The first step in optimizing the amount of work in a loop is
to minimize the number of object member and array item lookups. As
discussed in Chapter 2, these take
significantly longer to access in most browsers versus local
variables or literal values. The previous examples do a property
lookup for items.length
each and
every time through the loop. Doing so is wasteful, as this value
won’t change during the execution of the loop and is therefore an
unnecessary performance hit. You can improve the loop performance
easily by doing the property lookup once, storing the value in a
local variable, and then using that variable in the control
condition:
//minimizing property lookups for (var i=0, len=items.length; i < len; i++){ process(items[i]); } var j=0, count = items.length; while (j < count){ process(items[j++]); } var k=0, num = items.length; do { process(items[k++]); } while (k < num);
Each of these rewritten loops makes a single property lookup for the array length prior to the loop executing. This allows the control condition to be comprised solely of local variables and therefore run much faster. Depending on the length of the array, you can save around 25% off the total loop execution time in most browsers (and up to 50% in Internet Explorer).
You can also increase the performance of loops by reversing their order. Frequently, the order in which array items are processed is irrelevant to the task, and so starting at the last item and processing toward the first item is an acceptable alternative. Reversing loop order is a common performance optimization in programming languages but generally isn’t very well understood. In JavaScript, reversing a loop does result in a small performance improvement for loops, provided that you eliminate extra operations as a result:
//minimizing property lookups and reversing for (var i=items.length; i--; ){ process(items[i]); } var j = items.length; while (j--){ process(items[j]); } var k = items.length-1; do { process(items[k]); } while (k--);
The loops in this example are reversed and combine the control
condition with the decrement operation. Each control condition is
now simply a comparison against zero. Control conditions are
compared against the value true
,
and any nonzero number is automatically coerced to true
, making zero the equivalent of
false
. Effectively, the control
condition has been changed from two comparisons (is the iterator
less than the total and is that equal to true
?) to just a single comparison (is the
value true
?). Cutting down from
two comparisons per iteration to one speeds up the loops even
further. By reversing loops and minimizing property lookups, you can
see execution times that are up to 50%–60% faster than the
original.
As a comparison to the originals, here are the operations being performed per iteration for these loops:
One comparison (
i == true
) in the control conditionOne decrement operation (
i--
)One array lookup (
items[i]
)One function call (
process(items[i])
)
The new loop code has two fewer operations per iteration, which can lead to increasing performance gains as the number of iterations increases.
Decreasing the number of iterations
Even the fastest code in a loop body will add up when iterated thousands of times. Additionally, there is a small amount of performance overhead associated with executing a loop body, which just adds to the overall execution time. Decreasing the number of iterations throughout the loop can therefore lead to greater performance gains. The most well known approach to limiting loop iterations is a pattern called Duff’s Device.
Duff’s Device is a technique of unrolling loop bodies so that each iteration actually does the job of many iterations. Jeff Greenberg is credited with the first published port of Duff’s Device to JavaScript from its original implementation in C. A typical implementation looks like this:
//credit: Jeff Greenberg var iterations = Math.floor(items.length / 8), startAt = items.length % 8, i = 0; do { switch(startAt){ case 0: process(items[i++]); case 7: process(items[i++]); case 6: process(items[i++]); case 5: process(items[i++]); case 4: process(items[i++]); case 3: process(items[i++]); case 2: process(items[i++]); case 1: process(items[i++]); } startAt = 0; } while (iterations--);
The basic idea behind this Duff’s Device implementation is
that each trip through the loop is allowed a maximum of eight calls
to process()
. The number of
iterations through the loop is determined by dividing the total
number of items by eight. Because not all numbers are evenly
divisible by eight, the startAt
variable holds the remainder and indicates how many calls to
process()
will occur in the first
trip through the loop. If there were 12 items, then the first trip
through the loop would call process()
4 times, and then the second
trip would call process()
8
times, for a total of two trips through the loop instead of
12.
A slightly faster version of this algorithm removes the
switch
statement and separates
the remainder processing from the main processing:
//credit: Jeff Greenberg var iterations = items.length % 8; i = items.length -1; while(iterations){ process(items[i--]); iterations--; } iterations = Math.floor(items.length / 8); while(iterations){ process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); process(items[i--]); iterations--; }
Even though this implementation is now two loops instead of
one, it runs faster than the original by removing the switch
statement from the loop
body.
Whether or not it’s worthwhile to use Duff’s Device, either the original or the modified version, depends largely on the number of iterations you’re already doing. In cases where the loop iterations are less than 1,000, you’re likely to see only an insignificant amount of performance improvement over using a regular loop construct. As the number of iterations increases past 1,000, however, the efficacy of Duff’s Device increases significantly. At 500,000 iterations, for instance, the execution time is up to 70% less than a regular loop.
Function-Based Iteration
The fifth edition of ECMA-262 introduced a new method on
the native array
object called
forEach()
. This method iterates over the members of an array and
runs a function on each. The function to be run on each item is passed
into forEach()
as an argument and
will receive three arguments when called, which are the array item
value, the index of the array item, and the array itself. The
following is an example usage:
items.forEach(function(value, index, array){ process(value); });
The forEach()
method is
implemented natively in Firefox, Chrome, and Safari. Additionally,
most JavaScript libraries have the logical equivalent:
//YUI 3 Y.Array.each(items, function(value, index, array){ process(value); }); //jQuery jQuery.each(items, function(index, value){ process(value); }); //Dojo dojo.forEach(items, function(value, index, array){ process(value); }); //Prototype items.each(function(value, index){ process(value); }); //MooTools $each(items, function(value, index){ process(value); });
Even though function-based iteration represents a more convenient method of iteration, it is also quite a bit slower than loop-based iteration. The slowdown can be accounted for by the overhead associated with an extra method being called on each array item. In all cases, function-based iteration takes up to eight times as long as loop-based iteration and therefore isn’t a suitable approach when execution time is a significant concern.
Conditionals
Similar in nature to loops, conditionals determine how
execution flows through JavaScript. The traditional argument of whether
to use if-else
statements or a
switch
statement applies to
JavaScript just as it does to other languages. Since different browsers
have implemented different flow control optimizations, it is not always
clear which technique to use.
if-else Versus switch
The prevailing theory on using if-else
versus switch
is based on the number of conditions
being tested: the larger the number of conditions, the more inclined
you are to use a switch
instead of
if-else
. This typically comes down
to which code is easier to read. The argument is that if-else
is easier to read when there are
fewer conditions and switch
is
easier to read when the number of conditions is large. Consider the
following:
if (found){ //do something } else { //do something else } switch(found){ case true: //do something break; default: //do something else }
Though both pieces of code perform the same task, many would
argue that the if-else
statement is
much easier to read than the switch
. Increasing the number of conditions,
however, usually reverses that opinion:
if (color == "red"){ //do something } else if (color == "blue"){ //do something } else if (color == "brown"){ //do something } else if (color == "black"){ //do something } else { //do something } switch (color){ case "red": //do something break; case "blue": //do something break; case "brown": //do something break; case "black": //do something break; default: //do something }
Most would consider the switch
statement in this code to be more
readable than the if-else
statement.
As it turns out, the switch
statement is faster in most cases when compared to if-else
, but significantly faster only when
the number of conditions is large. The primary difference in
performance between the two is that the incremental cost of an
additional condition is larger for if-else
than it is for switch
. Therefore, our natural inclination
to use if-else
for a small number
of conditions and a switch
statement for a larger number of conditions is exactly the right
advice when considering performance.
Generally speaking, if-else
is best used when there are two discrete values or a few different
ranges of values for which to test. When there are more than two
discrete values for which to test, the switch
statement is the most optimal
choice.
Optimizing if-else
When optimizing if-else
, the goal is always to minimize the
number of conditions to evaluate before taking the correct path. The
easiest optimization is therefore to ensure that the most common
conditions are first. Consider the following:
if (value < 5) { //do something } else if (value > 5 && value < 10) { //do something } else { //do something }
This code is optimal only if value
is most frequently less than 5. If
value
is typically greater than or
equal to 10, then two conditions must be evaluated each time before
the correct path is taken, ultimately increasing the average amount of
time spent in this statement. Conditions in an if-else
should always be ordered from most
likely to least likely to ensure the fastest possible execution
time.
Another approach to minimizing condition evaluations is to
organize the if-else
into a series
of nested if-else
statements. Using
a single, large if-else
typically
leads to slower overall execution time as each additional condition is
evaluated. For example:
if (value == 0){ return result0; } else if (value == 1){ return result1; } else if (value == 2){ return result2; } else if (value == 3){ return result3; } else if (value == 4){ return result4; } else if (value == 5){ return result5; } else if (value == 6){ return result6; } else if (value == 7){ return result7; } else if (value == 8){ return result8; } else if (value == 9){ return result9; } else { return result10; }
With this if-else
statement,
the maximum number of conditions to evaluate is 10. This slows down
the average execution time if you assume that the possible values for
value
are evenly distributed
between 0 and 10. To minimize the number of conditions to evaluate,
the code can be rewritten into a series of nested if-else
statements, such as:
if (value < 6){ if (value < 3){ if (value == 0){ return result0; } else if (value == 1){ return result1; } else { return result2; } } else { if (value == 3){ return result3; } else if (value == 4){ return result4; } else { return result5; } } } else { if (value < 8){ if (value == 6){ return result6; } else { return result7; } } else { if (value == 8){ return result8; } else if (value == 9){ return result9; } else { return result10; } } }
The rewritten if-else
statement has a maximum number of four condition evaluations each time
through. This is achieved by applying a binary-search-like approach,
splitting the possible values into a series of ranges to check and
then drilling down further in that section. The average amount of time
it takes to execute this code is roughly half of the time it takes to
execute the previous if-else
statement when the values are evenly distributed between 0 and 10.
This approach is best when there are ranges of values for which to
test (as opposed to discrete values, in which case a switch
statement is typically more
appropriate).
Lookup Tables
Sometimes the best approach to conditionals is to avoid
using if-else
and switch
altogether. When there are a large
number of discrete values for which to test, both if-else
and switch
are significantly slower than using a
lookup table. Lookup tables can be created using arrays or regular
objects in JavaScript, and accessing data from a lookup table is much
faster than using if-else
or
switch
, especially when the number
of conditions is large (see Figure 4-1).
Lookup tables are not only very fast in comparison to if-else
and switch
, but they also help to make code more
readable when there are a large number of discrete values for which to
test. For example, switch
statements start to get unwieldy when large, such as:
switch(value){ case 0: return result0; case 1: return result1; case 2: return result2; case 3: return result3; case 4: return result4; case 5: return result5; case 6: return result6; case 7: return result7; case 8: return result8; case 9: return result9; default: return result10; }
The amount of space that this switch
statement occupies in code is
probably not proportional to its importance. The entire structure can
be replaced by using an array as a lookup table:
//define the array of results var results = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10] //return the correct result return results[value];
When using a lookup table, you have completely eliminated all condition evaluations. The operation becomes either an array item lookup or an object member lookup. This is a major advantage for lookup tables: since there are no conditions to evaluate, there is little or no additional overhead as the number of possible values increases.
Lookup tables are most useful when there is logical mapping
between a single key and a single value (as in the previous example).
A switch
statement is more
appropriate when each key requires a unique action or set of actions
to take place.
Recursion
Complex algorithms are typically made easier by using recursion. In fact, there are some traditional algorithms that presume recursion as the implementation, such as a function to return factorials:
function factorial(n){ if (n == 0){ return 1; } else { return n * factorial(n-1); } }
The problem with recursive functions is that an ill-defined or missing terminal condition can lead to long execution times that freeze the user interface. Further, recursive functions are more likely to run into browser call stack size limits.
Call Stack Limits
The amount of recursion supported by JavaScript engines varies and is directly related to the size of the JavaScript call stack. With the exception of Internet Explorer, for which the call stack is related to available system memory, all other browsers have static call stack limits. The call stack size for the most recent browser versions is relatively high compared to older browsers (Safari 2, for instance, had a call stack size of 100). Figure 4-2 shows call stack sizes over the major browsers.
When you exceed the maximum call stack size by introducing too much recursion, the browser will error out with one of the following messages:
Internet Explorer: “Stack overflow at line x”
Firefox: “Too much recursion”
Safari: “Maximum call stack size exceeded”
Opera: “Abort (control stack overflow)”
Chrome is the only browser that doesn’t display a message to the user when the call stack size has been exceeded.
Perhaps the most interesting part of stack overflow errors is
that they are actual JavaScript errors in some browsers, and can
therefore be trapped using a try-catch
statement. The exception type varies based on the browser being
used. In Firefox, it’s an InternalError
; in Safari and Chrome, it’s a
RangeError
; and Internet Explorer
throws a generic Error
type. (Opera
doesn’t throw an error; it just stops the JavaScript engine.) This
makes it possible to handle such errors right from JavaScript:
try { recurse(); } catch (ex){ alert("Too much recursion!"); }
If left unhandled, these errors bubble up as any other error would (in Firefox, it ends up in the Firebug and error consoles; in Safari/Chrome it shows up in the JavaScript console), except in Internet Explorer. IE will not only display a JavaScript error, but will also display a dialog box that looks just like an alert with the stack overflow message.
Recursion Patterns
When you run into a call stack size limit, your first
step should be to identify any instances of recursion in the code. To
that end, there are two recursive patterns to be aware of. The first
is the straightforward recursive pattern represented in the factorial()
function shown earlier, when a function calls itself. The general
pattern is as follows:
function recurse(){ recurse(); } recurse();
This pattern is typically easy to identify when errors occur. A second, subtler pattern involves two functions:
function first(){ second(); } function second(){ first(); } first();
In this recursion pattern, two functions each call the other, such that an infinite loop is formed. This is the more troubling pattern and a far more difficult one to identify in large code bases.
Most call stack errors are related to one of these two recursion patterns. A frequent cause of stack overflow is an incorrect terminal condition, so the first step after identifying the pattern is to validate the terminal condition. If the terminal condition is correct, then the algorithm contains too much recursion to safely be run in the browser and should be changed to use iteration, memoization, or both.
Iteration
Any algorithm that can be implemented using recursion can also be implemented using iteration. Iterative algorithms typically consist of several different loops performing different aspects of the process, and thus introduce their own performance issues. However, using optimized loops in place of long-running recursive functions can result in performance improvements due to the lower overhead of loops versus that of executing a function.
As an example, the merge sort algorithm is most frequently implemented using recursion. A simple JavaScript implementation of merge sort is as follows:
function merge(left, right){ var result = []; while (left.length > 0 && right.length > 0){ if (left[0] < right[0]){ result.push(left.shift()); } else { result.push(right.shift()); } } return result.concat(left).concat(right); } function mergeSort(items){ if (items.length == 1) { return items; } var middle = Math.floor(items.length / 2), left = items.slice(0, middle), right = items.slice(middle); return merge(mergeSort(left), mergeSort(right)); }
The code for this merge sort is fairly simple and
straightforward, but the mergeSort()
function itself ends up getting called very frequently. Recursive
functions are a very common cause of stack overflow errors in the
browser.
Running into the stack overflow error doesn’t necessarily mean the entire algorithm has to change; it simply means that recursion isn’t the best implementation. The merge sort algorithm can also be implemented using iteration, such as:
//uses the same merge() function from previous example function mergeSort(items){ if (items.length == 1) { return items; } var work = []; for (var i=0, len=items.length; i < len; i++){ work.push([items[i]]); } work.push([]); //in case of odd number of items for (var lim=len; lim > 1; lim = Math.floor((lim+1)/2)){ for (var j=0,k=0; k < lim; j++, k+=2){ work[j] = merge(work[k], work[k+1]); } work[j] = []; //in case of odd number of items } return work[0]; }
This implementation of mergeSort()
does the same work as the
previous one without using recursion. Although the iterative version
of merge sort may be somewhat slower than the recursive option, it
doesn’t have the same call stack impact as the recursive version.
Switching recursive algorithms to iterative ones is just one of the
options for avoiding stack overflow errors.
Memoization
Work avoidance is the best performance optimization technique. The less work your code has to do, the faster it executes. Along those lines, it also makes sense to avoid work repetition. Performing the same task multiple times is a waste of execution time. Memoization is an approach to avoid work repetition by caching previous calculations for later reuse, which makes memoization a useful technique for recursive algorithms.
When recursive functions are called multiple times during code
execution, there tends to be a lot of work duplication. The factorial()
function, introduced earlier in Recursion, is a
great example of how work can be repeated multiple times by recursive
functions. Consider the following code:
var fact6 = factorial(6); var fact5 = factorial(5); var fact4 = factorial(4);
This code produces three factorials and results in the factorial()
function being called a total of
18 times. The worst part of this code is that all of the necessary
work is completed on the first line. Since the factorial of 6 is equal
to 6 multiplied by the factorial of 5, the factorial of 5 is being
calculated twice. Even worse, the factorial of 4 is being calculated
three times. It makes far more sense to save those calculations and
reuse them instead of starting over anew with each function
call.
You can rewrite the factorial()
function to make use of
memoization in the following way:
function memfactorial(n){ if (!memfactorial.cache){ memfactorial.cache = { "0": 1, "1": 1 }; } if (!memfactorial.cache.hasOwnProperty(n)){ memfactorial.cache[n] = n * memfactorial(n-1); } return memfactorial.cache[n]; }
The key to this memoized version of the factorial function is
the creation of a cache object. This object is stored on the function
itself and is prepopulated with the two simplest factorials: 0 and 1.
Before calculating a factorial, this cache is checked to see whether
the calculation has already been performed. No cache value means the
calculation must be done for the first time and the result stored in
the cache for later usage. This function is used in the same manner as
the original factorial()
function:
var fact6 = memfactorial(6); var fact5 = memfactorial(5); var fact4 = memfactorial(4);
This code returns three different factorials but makes a total
of eight calls to memfactorial()
. Since all of the
necessary calculations are completed on the first line, the next two
lines need not perform any recursion because cached values are
returned.
The memoization process may be slightly different for each
recursive function, but generally the same pattern applies. To make
memoizing a function easier, you can define a memoize()
function that encapsulates the
basic functionality. For example:
function memoize(fundamental, cache){ cache = cache || {}; var shell = function(arg){ if (!cache.hasOwnProperty(arg)){ cache[arg] = fundamental(arg); } return cache[arg]; }; return shell; }
This memoize()
function
accepts two arguments: a function to memoize and an optional cache
object. The cache object can be passed in if you’d like to prefill
some values; otherwise a new cache object is created. A shell function
is then created that wraps the original (fundamental
) and ensures that a new result
is calculated only if it has never previously been calculated. This
shell function is returned so that you can call it directly, such
as:
//memoize the factorial function var memfactorial = memoize(factorial, { "0": 1, "1": 1 }); //call the new function var fact6 = memfactorial(6); var fact5 = memfactorial(5); var fact4 = memfactorial(4);
Generic memoization of this type is less optimal than manually
updating the algorithm for a given function because the memoize()
function caches the result of a
function call with specific arguments. Recursive calls, therefore, are
saved only when the shell function is called multiple times with the
same arguments. For this reason, it’s better to manually implement
memoization in those functions that have significant performance
issues rather than apply a generic memoization solution.
Summary
Just as with other programming languages, the way that you factor your code and the algorithm you choose affects the execution time of JavaScript. Unlike other programming languages, JavaScript has a restricted set of resources from which to draw, so optimization techniques are even more important.
The
for
,while
, anddo-while
loops all have similar performance characteristics, and so no one loop type is significantly faster or slower than the others.Avoid the
for-in
loop unless you need to iterate over a number of unknown object properties.The best ways to improve loop performance are to decrease the amount of work done per iteration and decrease the number of loop iterations.
Generally speaking,
switch
is always faster thanif-else
, but isn’t always the best solution.Lookup tables are a faster alternative to multiple condition evaluation using
if-else
orswitch
.Browser call stack size limits the amount of recursion that JavaScript is allowed to perform; stack overflow errors prevent the rest of the code from executing.
If you run into a stack overflow error, change the method to an iterative algorithm or make use of memoization to avoid work repetition.
The larger the amount of code being executed, the larger the performance gain realized from using these strategies.
Get High Performance JavaScript now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.