Jump to content

The Hazards of short circuits

A brief introduction

Short circuits are a “hack” folks use in JavaScript to do something conditionally abusing the way JavaScript evaluates the && and || operators. With the “recent” introduction of the ?? operator and the already existing ternaries, it doesn’t make sense to use short circuits anymore, but I still see them in code bases from time to time. In this article, I’ll go over examples of short circuits, why they are bad, and what alternatives you can use instead.

The basics of short circuits

The two leading short-circuit operators in JavaScript are the logical AND (&&) and logical OR (||). These operators evaluate expressions and return a value based on the truthiness or falsiness of the operands involved.

The logical AND (&&) operator evaluates to the value of the first falsy operand, or the last truthy operand if all operands are truthy. It performs short circuit evaluation, meaning that if the first operand is falsy, it doesn’t evaluate the second operand as the result would always be falsy.

1
return 0 && "Hello"; // Return 0
2
return undefined && "World"; // Return undefined
3
return "" && "World"; // Return ""
4
return "Hello" && "World"; // Return "World"
5
0 && someFunction(); // someFunction() is not called
6
1 && someFunction(); // someFunction() is called

On the other hand, the logical OR (||) operator returns the value of the first truthy operand, or the last falsy operand if all operands are falsy. It performs short circuit evaluation, meaning that if the first operand is truthy, it doesn’t evaluate the second operand as the result would always be truthy.

1
return 0 || "Hello"; // Return "Hello"
2
return undefined || "World"; // Return "World"
3
return "" || "World"; // Return "World"
4
return "Hello" || "World"; // Return "Hello"
5
0 || someFunction(); // someFunction() is called
6
1 || someFunction(); // someFunction() is not called

The Dangers of short circuits

While short circuits can be useful in certain scenarios, they can introduce subtle bugs and make code harder to reason about. Here are some reasons why short circuits can be problematic:

Lack of Readability

Every scenario where we use a short circuit, we could be using a ternary, nullish coalescing, or even an if statement, and it would be more readable. The more we rely on short circuits, the harder it is to reason about our code:

1
return value || "default";
2
3
// vs
4
5
return value ?? "default";
6
7
// or maybe...
8
9
return value !== undefined ? value : "default";
10
11
// or even ...
12
if (value !== undefined) {
13
return value;
14
} else {
15
return "default";
16
}

Hidden Side Effects

Is really easy to introduce side effects accidentally by using short circuits. This is because of the nature of JavaScript expression evaluation, making so if something is evaluated as falsy or truthy, then based on the operator we use (&& or || then we might return said value unintentionally):

1
`${value && "value"}`; // Evaluates to "false" if `value` is `false`
2
3
// vs
4
5
`${value ? "value" : ""}`; // Evaluates to "" if `value` is `false`

This also applies to some cases for JSX code:

1
// If `length` is `0`, then this evaluates to <div>0</div>, rendering a `0` in the DOM:
2
<div>{length && "has items"}</div>;
3
4
// vs
5
6
// If `length` is `0`, we have a fallback for it:
7
<div>{length ? "has items" : "is empty"}</div>;
8
9
// or better yet, to make the code as readable as it has to be:
10
<div>{length > 0 ? "has items" : "is empty"}</div>;

Implicit Type Coercion

Ideally, when we have conditional logic, it should rely on boolean logic, not falsy/truthy values, tho this applies to conditions in general, not only short circuits. This is because the intention of the code is way clearer:

1
username || "guest"; // Relying on `""` being falsy
2
length || "no items"; // Relying on `0` being falsy
3
4
// vs
5
6
username !== "" ? username : "guest"; // Using an actual comparison
7
length > 0 ? length : "no items"; // Using an actual comparison

The second example is way more readable and doesn’t rely in a flaky type coercion.

Alternatives to short circuits

To mitigate the issues associated with short circuits, it is advisable to consider alternative approaches that promote code clarity, maintainability, and reliability. Here are a few alternatives to consider:

Nullish coalescing and optional chaining

One of the most common uses of short circuits is to access a property that might be undefined, and to have a default value for it, like this:

1
return (object && object.property) || "default";

This can be problematic if object.property is 0 for example, or any other falsy value which would be valid, but using || we fallback to "default".

Nowadays we have tools designed for this very thing, that are way better because instead of relying in truthiness, they rely in nullish values (null or undefined):

1
return object?.property ?? "default";

This last example works pretty similarly to the one before, but if the value of object.property is 0, it will evaluate to 0 instead of "default". It only goes to "default" if object or object.property is null or undefined.

Ternaries

Continuing with the previous example, we might actually want to have a default value if the value is 0, but then we go back to the conversation about readability. Is better to express it like this:

1
const value = object?.property ?? 0;
2
3
return value === 0 ? "default" : value;

Longer? Yes, but also it makes our intention way clearer than value || "default".

Conclusion

Generally speaking, hacky code is bad code, because it’s exploiting a language “effect” as it was a language “feature”, so relying on any code that does this is not the best idea in the world. Short circuits are some of the worst kinds of hacky code because is not only abusing the language, but is also making it harder to maintain and read. The “benefits” (such as “writing less” and “feeling smart”) are heavily outweighed by the issues they introduce.

Next time you’re considering using a short circuit, make yourself and everyone else a favor and use language features designed for what you’re trying to achieve.