OR operator “||” is useful to set a default value if the target variable is not set but it is easy to make a mistake even though a programmer is familiar with it. It doesn’t tell clear intention in some cases and can cause a bug. Let’s check how to use OR operator and how we tend to create a bug with it.
Setting a default value if a variable is empty
The use case of the OR operator is assigning a default value to the variable. We can check if the variable is empty in the following way and assign the default value.
function getValueOrDefault(value: unknown) {
return value || "default value;
}
console.log(getValueOrDefault(undefined)); // default value
It returns the default value since the value is undefined. It works as expected and there seems to be no problem… Yes, as long as the value is undefined. The following code does the same thing in a different way. It checks if the value is undefined.
function getTitle(value: unknown) {
let title = value;
if (Array.isArray(value)) {
title = "[]";
} else if (typeof value === "string") {
title = '""';
}
return title;
}
function ternaryOperator1(value: unknown) {
const result = value !== undefined ? value : "default value";
console.log(`${getTitle(value)}\t: ${result}`);
}
const values = [undefined, null, "", 0, [], false];
values.forEach(ternaryOperator1);
// undefined : default value
// null : null
// "" :
// 0 : 0
// [] :
// false : false
Of course, it returns the default value only if the value is undefined. Let’s remove the undefined check from here.
function ternaryOperator2(value: unknown) {
const result = value ? value : "default value";
console.log(`${getTitle(value)}\t: ${result}`);
}
const values = [undefined, null, "", 0, [], false];
values.forEach(ternaryOperator2);
// undefined : default value
// null : default value
// "" : default value
// 0 : default value
// [] :
// false : default value
The result is different from the previous example. An only empty array is handled as “Not Empty” variable. If we remove the undefined check from the ternary operator its behavior is different. We must know this because the code above is the same as the following.
function orOperator1(value: unknown) {
const result = value || "default value";
console.log(`${getTitle(value)}\t: ${result}`);
}
const values = [undefined, null, "", 0, [], false];
values.forEach(orOperator1);
// undefined : default value
// null : default value
// "" : default value
// 0 : default value
// [] :
// false : default value
The result is the same as the one before. This is often used to assign a default value. For the string variable, this behavior might be natural in most cases but look at the result of 0 and false. This code can be used but it is not clear whether it’s correct or not. This mistake is easily introduced even if the programmer is familiar with it.
In my opinion, we should avoid this usage if possible because its intention is not clear.
An example that tends to cause a bug
Look at the following code.
function setInterval1(value: number) {
return value || 10;
}
console.log(setInterval1(undefined as any)); // 10
console.log(setInterval1(null as any)); // 10
console.log(setInterval1(0)); // 10
console.log(setInterval1(1)); // 1
If the value is 0 it returns 10. Is it the correct code? It depends on the system. If the value is 0, a user may want to stop the timer or may simply forget to assign the value. This is not a good code because its intention is not clear. We have to read the specification to know whether it is a bug or not. It takes us additional time to find the specification.
Let’s look at another example.
function getEnableState1(value: boolean | undefined | null) {
const title = getTitle(value);
console.log(`${title}\t ${value || false}`);
console.log(`${title}\t ${value || true}`);
}
[undefined, null, false, true].forEach(getEnableState1);
// undefined false
// undefined true
// null false
// null true
// false false
// false true <-- Is this correct?
// true true
// true true
Look at the result. If the value is false and the default value is true, its result becomes true. Is it correct behavior? This can be a bug if a client gets a service list and wants to get current enable states for all services. Default value true is set to the services that a value hasn’t been assigned yet. However, true is assigned to the service as well that has already been disabled.
Better solution for assigning default value
Let’s consider how we can achieve the same thing in a different way whose code has a clear intention.
function setInterval2(value: number) {
return value ?? 10;
}
console.log(setInterval2(undefined as any)); // 10
console.log(setInterval2(null as any)); // 10
console.log(setInterval2(0)); // 0
console.log(setInterval2(1)); // 1
This code has clear intention, doesn’t it? If a user specifies 0, it means that the timer stops. If it is undefined or null, a user doesn’t set the value and wants to use the default value. If we want to set a default value when the value is 0, the code becomes the following.
function setInterval3(value: number) {
const currentOrDefault = value ?? 10;
return currentOrDefault !== 0 ? currentOrDefault : 10;
}
console.log(setInterval3(undefined as any)); // 10
console.log(setInterval3(null as any)); // 10
console.log(setInterval3(0)); // 10
console.log(setInterval3(1)); // 1
Let’s replace the OR operator in the previous code with NULL operator (Nullish Coalescing operator).
function getEnableState2(value: boolean | undefined | null) {
const title = getTitle(value);
console.log(`${title}\t ${value ?? false}`);
console.log(`${title}\t ${value ?? true}`);
}
// undefined false
// undefined true
// null false
// null true
// false false
// false false
// true true
// true true
I guess no one suspects that this might be a bug.
End
OR operator is useful but we should be aware of the difference for the different data type. It can easily introduce a bug to our software. It’s better to discuss this with your team members and have a coding rule.
Comments