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.
return 0 && "Hello"; // Return 0return undefined && "World"; // Return undefinedreturn "" && "World"; // Return ""return "Hello" && "World"; // Return "World"0 && someFunction(); // someFunction() is not called1 && 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.
return 0 || "Hello"; // Return "Hello"return undefined || "World"; // Return "World"return "" || "World"; // Return "World"return "Hello" || "World"; // Return "Hello"0 || someFunction(); // someFunction() is called1 || 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:
return value || "default";
// vs
return value ?? "default";
// or maybe...
return value !== undefined ? value : "default";
// or even ...if (value !== undefined) { return value;} else { return "default";}
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):
`${value && "value"}`; // Evaluates to "false" if `value` is `false`
// vs
`${value ? "value" : ""}`; // Evaluates to "" if `value` is `false`
This also applies to some cases for JSX code:
// If `length` is `0`, then this evaluates to <div>0</div>, rendering a `0` in the DOM:<div>{length && "has items"}</div>;
// vs
// If `length` is `0`, we have a fallback for it:<div>{length ? "has items" : "is empty"}</div>;
// or better yet, to make the code as readable as it has to be:<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:
username || "guest"; // Relying on `""` being falsylength || "no items"; // Relying on `0` being falsy
// vs
username !== "" ? username : "guest"; // Using an actual comparisonlength > 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:
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
):
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:
const value = object?.property ?? 0;
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.