No ifs…alternatives to statement branching in JavaScript

You could do this..

//Example 1
function getEventTarget(evt) {
    if (!evt) {
    	evt = window.event;
    }
    if (!evt) {
    	return;
    }
    var target;
    if (evt.target) {
        target = evt.target;
    } else {
        target = evt.srcElement;
    }
    return target;
}

or you could do this…

//Example 2
function getEventTarget(evt) {
    evt = evt || window.event;
    return evt && (evt.target || evt.srcElement);
}


To avoid ambiguity I’ll explain how I’m using a couple of terms in this article:
statement branching: any construct that alters the sequence of statement execution within the global or functional scope. These include if, else, switch, for and while statements.
micro-branching: conditional logic contained within a statement that has no effect on the statement execution seqeunce. The following operators facilitate micro-branching: ternary, && and ||.

OK back to the examples…

Example 1 employs statement branching, i.e. its statements are designed to execute non-linearly. Aside from the obvious bloating effect, statement branching tends to become unintuitive as it progresses (its really just one step up on the food chain from the widely discredited goto statement). As humans we are inclined to read function code top-down, line by line through to the last line. Logic built on statement branching forces us to mentally track each possible execution sequence. As branching logic gets more complex the number of forking paths increases and it becomes easy to overlook edge-case flow scenarios. Bugs love overlooked scenarios.

Example 2 uses micro-branching. The logic flows sequentially from top to bottom and even from left to right. There are no forks in the road. There is only one return statement and its at the bottom where we expect it. Best of all it’s short. Nothing is wasted. In fact it’s terse enough to be barely procedural at all.

Statement branching is necessary and useful but having the full toolkit of alternatives at hand helps to keep our JavaScript readable, concise and robust. There’s also an ideological element here: there’s a lot of power in JavaScript’s functional capabilities, but we need to let go of some procedural baggage in order to unleash it.

Alternatives to statement branching fall into two broad categories: micro-branching and no branching at all. Let’s dig deeper into each:

Micro-branching strategies

Guards (&&) and Defaults(||)

The logical boolean operators && and || are familiar syntax in many languages.
JavaScript has a liberal approach to these operators:
• Constructs formed by logical boolean operators can be used as expressions within statements or can form the entire statement
• The operands need not evaluate to booleans but will be coerced to booleans to facilitate the logical operation
• The result of evaluating a logical boolean expression need not be a boolean value
(see ECMA 5 11.11)

This provides for some gorgeously succinct non-branching conditional logic:

//invoke callback if there is one
callback && callback();
//delay by argument or 20
delayBy(delay || 20);
//remove node from its parent
node && node.parent && node.parent.removeChild(node);
//log a test in the console id we have one
window.console && console.log('test');

The Ternary operator

Also (and more clumsily) known as the conditional ?: operator, this is another cross-language standard which we can leverage to lay down conditions without affecting the sequence of statement execution.

When used badly ternary operators are no better than bad imitations of if/else branching. The perplexed smiley in the middle of this one says it all:

//Bad example - ternary that thinks its an if/else.
var a = 2, b = 1;
a ? (
	b++,
    a = a*2,
    console.log(a,b)
):(
    b--,
    a = a/2,
    alert(a + " " + b)
);

However when used as a conditional assignment or intra-statement switch, the logic is clean and easy to follow.

//make an array of the args if any, or return empty array
var args = arguments ? toArray(arguments) : [];
//end game or go to next level
gameOver ? finally() : nextLevel();

Function Delegation

As if/else blocks get bigger and/or nested they get harder to follow. If the statement block(s) are more than a few lines it generally suggests the need for an additional function or functions.

Here is a function for dropping a dragged item in a box. Firstly using if/else and multiple bailing returns….

function itemDropped(item, location) {
    if (!item) {
        return false;
    } else if (outOfBounds(location) {
        var error = outOfBounds;
        server.notify(item, error);
        items.resetAll();
        return false;
    } else {
        animateCanvas();
        server.notify(item, location);
        return true;
    }
}

…and secondly reimplemented to use function delegation, a ternary and one trailing return per function. Note the added bonus of being able to name your conditions – like a built-in comment.

function itemDropped(item, location) {
    var dropOut = function() {
        server.notify(item, outOfBounds);
        items.resetAll();
        return false;
    }

    var dropIn = function() {
        server.notify(item, location);
        animateCanvas();
        return true;
    }

    return !!item && (outOfBounds(location) ? dropOut() : dropIn());
}

Be careful with “bailing” returns

Some things just have a natural place. Birds in the sky, fish in the sea and a return statement at the end of a function. Short circuiting a function when an variable is null or some other non-useful value might be handy for developers but it can sometimes be a source of obfuscation for reviewers and bug fixers (and more often than not the bug fixer is the original developer). I’m as guilty as anyone else when it comes to bailing returns but other people’s code is often more readable without them. The example just above and Example 2 at the beginning of this article illustrate strategies for avoiding bailing returns.

Non-branching strategies

Property look-ups (a.k.a. dispatch tables)

My very first blog post touted my preference for hash look-ups over switch statments so I won’t re-hash(!) all the same arguments here. Suffice to say, functions are most expressive when they eschew data considerations and focus on form. Defining data dependent action properties elsewhere enables just such a separation.

Here’s an example that reacts to a toolbar button being clicked in a file manager type application. First using a switch. Note the clicked function is specific to fileManager and as a consequence we start to build up some ominous looking namespace chaining:

fileManager.toolbar.clicked = function(buttonId) {
    switch(buttonId) {
        case 'open': fileManager.openNew(true);
            break;
        case 'save': fileManager.saveSelected(true);
            break;
        case 'run': fileManager.executeSelected(true);
            break;
        default: coreUtils.notImplemented();
    }
}

fileManager.toolbar.clicked('save');

Now here’s an alternate implementation using a hash table for lookup. Adding a new button will be a breeze – just add a new property to the actions object. And the clicked function is now generic – action objects can be passed as parameters from any toolbar.

fileManager.toolbarActions = {
    'open': {fn: fileManager.openNew, args: [true]},
    'save': {fn: fileManager.saveSelected, args: [false]},
    'run': {fn: fileManager.execSelected, args: [false]},
    'default': {fn: coreUtils.notImplemented, ctxt: coreUtils},
}

toolbar.clicked = function(actions, buttonId) {
    var action = actions[buttonId] || actions['default'];
    action.fn.apply(action.ctxt, action.args);
}

toolbar.clicked(fileManager.toolbarActions, 'save');

Higher Order Functions

One of the de facto characteristics of Functional Programming is the use of higher order functions (functions into which other functions are injected as data) to encapsulate procedural logic. It’s very hard to write purely functional JavaScript – there will almost always be a reliance on state and in-function side effects – and at its heart the language is built on imperative logic; however it is possible to de-emphasize the imperative nature of the language (branching, loops, disruptors) and shift the emphasis toward functional building blocks. Again humans are much better at validating concepts than validating non-linear path logic.

Array functions

All the major JavaScript frameworks define a rich set of higher order functions for use with Arrays. ECMA 5 also defines a similar set of functions and they are already implemented in all browsers except for IE<=8.

(A note on performance – if your array is very large you might see some performance degradation with the higher order array function – every function call carries a small but cumulative cost. As with all coding – write it for sturdiness and readability, optimize later if you have to – and you probably won’t have to)

Consider a function that returns all words longer than four letters. First the naive approach. The array is short and the test is simple but the logic will still touch about 50 statements in a loopy-doopy sequence.The author is forced to churn out the same mundane looping syntax that she will probably repeat multiple times elsewhere. It’s donkey work that increases the probability of errors and only serves to obfuscate the more meaningful content.

function dropShortWords(words) {
    var wordArray = words.split(" ");
    var longWords = [];
    for (var i=0; i<wordArray.length; i++) {
        var word = wordArray[i];
        if (word.length>4) {
            longWords.push(word);
        }
    }
    return longWords.join(" ");
}

dropShortWords("The quick brown fox jumped over the lazy dog"); //"quick brown jumped"

… and here is we define the same function using the higher order filter function. Four lines and we left the looping and branching to the safety of an industry tested utility. Moreover with the distraction of the looping syntax removed, the intent of the function becomes clearer.

//(will not work in IE<9)
function dropShortWords(words) {
    var longWords = words.split(" ").filter(function(word){
        return word.length>4;
     });
     return longWords.join(" ");
}

dropShortWords("The quick brown fox jumped over the lazy dog"); //"quick brown jumped"
Functions as Data

Functions are first class objects in JavaScript and this allows us to pass them as parameters to other functions. Amongst other things, this provides an alternative to branching.

Here is a simple calculator. With ifs….

var calc = {
    run: function(op, n1, n2) {
        var result;
        if (op == "add") {
            result = n1 + n2;
        } else if (op == "sub" ) {
            result = n1 - n2;
        } else if (op == "mult" ) {
            result = n1 * n2;
        } else if (op == "div" ) {
            result = n1 / n2;
        }
        return result;
    }
}

calc.run("sub", 5, 3); //2

…and now using run as a higher order function instead:

var calc = {
    add : function(a,b) {
        return a + b;
    },
    sub : function(a,b) {
        return a - b;
    },
    mult : function(a,b) {
        return a * b;
    },
    div : function(a,b) {
        return a / b;
    },
    run: function(fn, a, b) {
        return fn && fn(a,b);
    }
}
calc.run(calc.mult, 7, 4); //28

Polymorphism

This strategy is well known to anyone versed in classical OOP. At its best it’s smart and intuitive. No longer does one method have to implement complex branching based on type – instead each type knows how to implement the method in it’s own way. However I must confess, these days its easy to get hierarchy fatigue. Even with the best IDEs, complex hierarchies have a tendency to be just as off-putting as a long and nested if else construct. No one can keep a mental model of a sophisticated class or object tree and latterly inserting a new member or method into that tree can be very painful. Adding objects to spaghetti code just gets you spaghetti with meatballs. Moreover, even though prototypical inheritance is a viable alternative to classical inheritance, in JavaScript I find I rarely have a genuine need for inheritance at all.

In the linked article I include an example of polymorphic messaging across types as an alternative to statement branching.

Putting it all together

I’m by no means the last authority on coding style and this article is as much about investigation as it is about recommendation. You should use your judgment to write code that is as tidy and as understandable as you can make it (subject to common-sense performance considerations). I hope this article helps a little bit towards that goal.

Comments and Questions are very welcome

40 thoughts on “No ifs…alternatives to statement branching in JavaScript

  1. Angus, keep in mind that using && as a “guard” is only useful if you know that a variable has already been declared (for example, if it is an optional function argument).

    In your console.log example, you’ve assumed that `console` has been declared, but in a browser that lacks a console, it wouldn’t be. As such, you should consider changing your test from `console && console.log(‘test’)` to `window.console && console.log(‘test’)` to avoid a “console is not defined” error.

  2. Thanks. I’d always thought of logic short-circuiting as an interesting hack, but not something I’d use very often. You’ve given me another point of view.

    There’s a minor error in the second listing in “Function Delegation”

    14 return !!item && (outOfBounds(location) ? dropOut() || dropIn());

    Shouldn’t the || be a :

    Also in “Property look-ups” could you explain why the default action needs a ctxt parameter but none of the the others do.

    1. Thanks Alex – good catch – I’ll fix right now

      The use of context in the property look-ups example was arbitrary. I was assuming the implementation of the default action would contain a this object which needs binding to the correct object – but this could have equally well applied to the other functions

  3. The word “default” is a Javascript keyword and so it should be quoted in the toolbarActions lookup. I think Safari/Chrome is picky about that.

  4. Your terminology is very bad. “Branching” is used to discuss the overhead chips face (most notably Intel) when confronted with multiple code paths based on some value (typically a variable, which takes time to get from memory).

    You’re replacing “branching” with more “branching.” Calling it micro-branching makes no sense given the meaning of the world. Branching relates to the CPU, and a || branch is the same in an if statement or out of it!

    The ARM6 (iPhone) chipset has conditional instructions. This means single instruction if statements do not have a branching overhead. But it doesn’t matter if you use an if or a || operator, you get the bonus either way!

    1. Dustin – thanks for your input. I don’t dispute your definition of branching. However two things to point out:
      1) The rationale for this exploration is readability not performance gain
      2) I think I was pretty clear that there is no difference in branching logic between ifs and guards/ternaries

      1. This should be better readability??? Give me a break! This is only shorter and remembers me of the competitions we had twenty years ago about getting any program on a single line (ahh…, the old C days). It is ultracool, yes, and you have a deep understanding, the points go to you. But what if someone without your programming level must debug such code? Their productvity will be in the basement.

      2. Hi Ben – no argument from me. As I think I said in my wrap up, you need to do what’s best for you and your team – these are just suggestions that if the whole team is on board with could work well.

        It’s interesting – about half total feedback loves these ideas the other half loathe them 🙂

    1. Thanks Ernest. I appreciate the positive feedback. I doubt if it will run faster. Some cases will run slower (see my performance advice in the higher order functions section) but any performance differences will usually be negligible. My mantra is write for readability first, optimize later only if necessary.

  5. In the example involving the two versions of the `itemDropped` method, I find the first one more readable. Everyone likes to write terse code, but people who have to read and maintain it are usually not such big fans.

    Also, readability opinions aside, whenever you are defining functions within a function, it is a good idea to define those functions in a closure to avoid the overhead of having to define them each time the outer function is called.

    var itemDropped = (function() {
    var dropOut = function() {…};
    var dropIn = function() {…};

    return function(item, location) {
    if ( !item ) {
    return false;
    }
    return outOfBounds(location) ? dropOut() : dropIn();
    };
    })();

    However, other than that, a good article indeed covering a topic that needs exposure.

    1. Justin – good point about closures – in fact after that optimization it will actually outperform the original “if” version.

      About readability. Lets just agree its in the eye of the beholder. I struggle to see why the second version isn’t more readable – each condition is now a named function, its more cleanly organized (and fwiw its actually no more terse than the first version). But I fully accept that one persons clarity is another’s mess. We should do what works for us and our team

  6. Your first two examples are not equivalent. Example one can return an event target while example two will always return a boolean value.

  7. Great article, though keep in mind, that the or-operator (||) you name as a Default, will eventually result in unexpected behaviour if the first value evaluates to false. See your example for delayBy:

    var delay = 0;
    delayBy(delay || 20);

    The example above will still delay by 20, as 0 evaluates to false. So you should use this with care when it comes to numbers or strings (“” also evaluates to false as far as I know).

    Cheers and keep up the good work on this blog. tips here are amazing.

    1. Thanks for the kind words MrF
      Fair point re. 0 evaluating to false – cases where 0 (or other falsey input) might be meaningful are not appropriate to the || operator – a ternary might be better:

      delayBy(undefined !== delay ? delay : 20)

      1. MrF made the point I was going to make, and you gave the alternative I was going to give, except: undefined is not a keyword in JavaScript.
        Sure, only a sadist would create a variable named undefined, but why risk it? Use typeof delay !== 'undefined' instead.

  8. This is wonderful stuff. How would you handle validation? Say you have a name and email. I’ll use jQuery for example because I know it well:

    var err = []
    $(“#emaddr”).val() && (
    /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test($(“#emaddr”).val())
    || err.push(‘Email Address must be in the form someone@somewhere.com‘)
    )
    || err.push(‘Email Address required’);

    $(“#name”).val() || err.push(‘Name required’);

    var fn = err.length? error_popup : save_user;
    fn(err);

    This has no if statements, but it still appears like it can be improved upon, mainly the double-conditional check on email and the function call (save_user does not need the err argument). Suggestions?

    1. Hi Tandu – thanks for the praise

      You probably just need to assign the error cases to variables first. This should clean it up nicely:

      var err = [];
      var emAddr = $(“#emaddr”).val();
      var emFormat = emAddr && /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(emAddr);
      
      emAddr || err.push(‘Email Address required’);
      emFormat || err.push(‘Email Address must be in the form someone@somewhere.com‘);
      $(“#name”).val() || err.push(‘Name required’);
      
      err.length ? errorPopup(err) : saveUser();
      
  9. I disagree with “Short circuiting a function when an variable is null or some other non-useful value might be handy for developers but it can sometimes be a source of obfuscation for reviewers and bug fixers”
    Something like :

    function abc(par) {
    if (!par)
    return;
    :
    // the rest of the function
    :
    }

    Is very good style in my opinion (yes my university lecturers warned against it too) and I find it very readable. I try to eliminate bad inputs and easily-responded-to scenarios as early as possible in a function, and then deal with the more complex scenarios later. It makes the complex code easier to read and write because the corner cases are eliminated from consideration.
    It also keeps if/then or case nesting to a minimum.

    1. Gary – I agree, if used well its cool, even elegant. I sometimes use bailing returns too. But to often I see multiple returns hidden away in unexpected places and the effect is to obfuscate. As with everything, balance is the key

Leave a reply to ernest leitch Cancel reply