Short circuit evaluation is a type of lazy evaluation where a boolean expression with ||
or &&
can return early if
the left-hand value suffices to determine the expression's value.
Conceptually, if the left-hand side of an ||
expression is true
, we don't care about the right; no matter what the
right-hand side evaluates to, we know the expression will always evaluate to true
. Similarly, if the left-hand side of
an &&
expression is false
, we don't need to know what the right-hand side evaluates to as we know that the expression
will always evaluate to false
.
If the left-hand side of an ||
expression is false
(or the right-hand side of an &&
expression true
), the value of
the expression depends only on what the right-hand side evaluates to. If you're having trouble visualizing this, use the
following table:
false || true -> true
false || false -> false
true && true -> true
true && false -> false
Therefore, a shortcut exists for evaluating these expressions. For a || b
, if a
is true then return a
.
Otherwise, simply return b
. Similarly, for a && b
, if a
is false then return a
; otherwise, return b
.
Because short-circuited checks return early if the left-hand information is sufficient to determine the result, when conditions are chained, short circuiting can be used to conditionally avoid harmful side effects in later conditions. The following examples are in Java:
// Return early if `user.name` is null, avoiding a null pointer exception.
if (user.name != null && user.name.length() > 5) {
// ...
}
// Return early if the denominator is 0, avoiding a divide by zero exception.
if (denom != 0 && num / denom > 1) {
// ...
}
Conceptually, an early return means that each check can establish certain "guarantees" for later conditions.
public boolean isFourthElementInvalid(String[] elements) {
// `elements[3]` is guaranteed to exist for the second condition because we return early if
// elements.length < 4. Similarly, `elements[3].charAt(1)` is guaranteed to be safe because we
// return early if elements[3].length() < 2.
return elements.length >= 4
&& elements[3].length() >= 2
&& elements[3].charAt(1) != 'V';
}
In TypeScript, the above can be used with type guards. Here, we guarantee that if the second condition is evaluated,
obj
is a number and is therefore safe to be compared with <
.
// In the right-hand side of the condition, obj: number
if (typeof obj === 'number' && obj < 5) {
console.log(`${obj} is less than 5`);
}
In dynamically typed languages like JavaScript and Scheme, short circuit evaluation can be paired with the concept of "truthiness".
In such languages, in boolean conditions like ||
or &&
, "falsy" values like ''
(the empty string),
0
, null
, or undefined
are automatically cast to false
while all other values are considered "truthy" and cast
to true
. Because short circuited conditions return a
and b
instead of necessarily true
or false
, they can be
used outside of boolean contexts to simplify common programming patterns.
For example, a common pattern is to conditionally assign default values for variables if the user doesn't supply a value themselves. One way to implement this is using a mutable variable whose value is changed inside an if statement:
async function ban(user: User, reason?: string) {
let parsedReason = reason;
// If reason is null or empty, use the string 'No reason provided' instead
if (!parsedReason) parsedReason = 'No reason provided';
await user.ban(parsedReason);
}
But this method may be unideal if it is preferable to keep the variable immutable. Conceptually, too, it shouldn't take two
assignments to parsedReason
to give it the default value when we know from the beginning that reason
is undefined.
One way to simplify this assignment is using a ternary expression:
async function ban(user: User, reason?: string) {
const parsedReason = reason ? reason : 'No reason provided';
await user.ban(parsedReason);
}
But reason ? reason
seems repetitive and redundant. Indeed, the assignment can be further simplified using a short circuited ||
:
async function ban(user: User, reason?: string) {
// If reason is non-null and non-empty, use it; otherwise, use the string 'No reason provided'
const parsedReason = reason || 'No reason provided';
await user.ban(parsedReason);
}
In fact, this pattern is so common that most languages have a null coalescing operator ??
which performs this short circuit
assignment specifically for when the left-hand value is nullish.
function printLocalStorageKey(key: string) {
const data = localStorage.get(key);
console.log(data ?? `LocalStorage key ${key} not found.`);
}
In React, short circuit evaluation powers conditional rendering:
export default function UserCard(props: User) {
return (
<div className="card">
{/* If any of these properties are null, return and discard it. */}
{/* Otherwise, render the corresponding tag. */}
{props.avatar && <img src={props.avatar} />}
{props.name && <h1>{props.name}</h1>}
{props.email && <h2>{props.email}</h2>}
{props.desc && <p>{props.desc}</p>}
</div>
)
}
Because the right-hand-side is never evaluated when an expression "short circuits", you can use it to simplify conditional
code execution if (x) y()
inside arrow functions or other expression contexts:
// Call `removeTodo(i)` if the input is checked, otherwise return early.
return <input type="checkbox" onChange={(e) => e.target.checked && removeTodo(i)} />