JavaScript conditional not short circuiting like I would expect

517 views Asked by At

I am an experienced developer, but I just ran into an issue that took me a while to figure out and am looking for an explanation. I was relying on short circuiting and spent more time than I care to admit debugging this. If this belongs on another Stack Exchange site, please advise.

I expect the following to evaluate to false, but it passes:

(false && true || true) => true

It is as though it's being interpreted as this:

((false && true) || true) => true

... but the solution is this:

(false && (true || true)) => false

Why doesn't false short circuit the operation in the first example? Is there some sort of lookahead I don't know about?


Solution summary: For those who (like me) never knew conditional operators have a similar precedence as mathematical operators - the same concept of implied parenthesis applies:

3 * 2 + 1 => (3 * 2) + 1 => 7
false && true || true => (false && true) || true => true
1

There are 1 answers

4
tckmn On BEST ANSWER

Logical AND (&&) has a higher precedence than logical OR (||).

You could fix the problem by adding the parentheses you used in your example.

x && (y || z);

However, it would be much more readable to use an explicit if statement instead:

if (x && y) {
    z;
}

Here's a reference chart for operator precedence in JS. (See #13 and #14.)

If you're having a hard time understanding precedence, try substituting * for && and + for ||:

x * y + z;

Clearly, by order of operations, the x * y will be executed first. So if you want to execute the y + z first instead, you would use parentheses.

Short circuiting has nothing to do with the syntax. It's simply a quirk of boolean operators. So don't think of it like

I always thought that the first failed condition short circuits the operation and further evaluation stops.

The short circuiting doesn't call some sort of "abort" function that exits the entire operation. It's simply that the boolean operators will ignore their second argument (b in a && b) if it's already ascertainable what the final result will be. (For example, false && (anything) is always false, so && is lazy and doesn't bother evaluating the second argument.)

I still don't get how false && (anything) is always false but false && (anything) || somethingelse can be true.

Okay, so applying the precedence rules, we get:

(false && anything) || somethingelse

Therefore, the && is evaluated first. Since it's lazy, it sees the false and immediately returns false:

(false) || somethingelse // `anything' is not evaluated

Now it's ||'s turn to evaluate. It sees the false that && just returned, and it can't short-circuit because false || true could still be true. So it has to evaluate the somethingelse to get the final result.

Therefore, the code if((false && anything) || somethingelse) is essentially equivalent to if (somethingelse).