TypeScript ?? vs || : Understanding the Difference
TypeScript, being a superset of JavaScript, inherits many of its operators, including the logical OR (||
) and the nullish coalescing operator (??
). While they might seem similar at first glance, especially when dealing with default values, they behave quite differently in crucial scenarios. Understanding this difference is vital for writing clean, predictable, and bug-free TypeScript code.
This article dives deep into the distinctions between ??
and ||
, providing clear examples and explanations to solidify your understanding.
1. The Logical OR Operator (||)
The logical OR operator (||
) has been a staple of JavaScript for a long time. It’s a short-circuiting operator, meaning it evaluates the left-hand side operand first.
- If the left-hand side is truthy, it returns the left-hand side value directly. It doesn’t evaluate the right-hand side at all.
- If the left-hand side is falsy, it returns the right-hand side value, regardless of whether the right-hand side is truthy or falsy.
Here’s the crucial part: falsy values in JavaScript include:
false
0
(zero, both positive and negative, and 0n BigInt)''
(empty string)null
undefined
NaN
Example:
``typescript
Hello, ${displayName}!`);
function greet(name: string | null) {
const displayName = name || "Guest";
console.log(
}
greet(“Alice”); // Output: Hello, Alice!
greet(null); // Output: Hello, Guest!
greet(“”); // Output: Hello, Guest! <– Important!
greet(undefined); //Output: Hello Guest!
greet(0); //Output: Hello Guest!
“`
In the example above, name || "Guest"
works as expected for null
and undefined
, providing the default “Guest” value. However, it also treats an empty string (""
) and 0
as falsy, and therefore uses the default “Guest” value even though ""
and 0
might be valid inputs in certain contexts (e.g., a user might have a deliberately empty name field, or a numerical value might legitimately be 0). This can lead to unexpected behavior.
2. The Nullish Coalescing Operator (??)
The nullish coalescing operator (??
) was introduced in ES2020 and is designed to address the limitations of ||
when dealing with default values. It also short-circuits, evaluating the left-hand side first.
- If the left-hand side is not nullish (i.e., it’s not
null
and notundefined
), it returns the left-hand side value. - If the left-hand side is nullish (either
null
orundefined
), it returns the right-hand side value.
Key Difference: ??
only considers null
and undefined
as “nullish.” All other falsy values (like 0
, ''
, false
, and NaN
) are treated as valid values and are not replaced by the right-hand side.
Example (using the same greet
function):
``typescript
Hello, ${displayName}!`);
function greet(name: string | null) {
const displayName = name ?? "Guest";
console.log(
}
greet(“Alice”); // Output: Hello, Alice!
greet(null); // Output: Hello, Guest!
greet(“”); // Output: Hello, ! <– Different!
greet(undefined); // Output: Hello, Guest!
greet(0); // will throw a type error:
// Argument of type ‘number’ is not assignable to parameter of type ‘string | null’.
function greet2(name: string | null | number) {
const displayName = name ?? “Guest”;
console.log(Hello, ${displayName}!
);
}
greet2(0); //Output: Hello, 0!
“`
Now, using ??
, the empty string (""
) is correctly treated as a valid value and is not replaced with “Guest.” The greet
function with ??
provides more accurate and predictable behavior when the input might be intentionally empty or zero. We fixed this behaviour by updating the type of name
.
3. When to Use Which Operator
-
Use
??
(nullish coalescing) when:- You want to provide a default value only if the variable is
null
orundefined
. - You want to treat other falsy values (like
0
,''
,false
) as valid inputs. - You are working with potentially optional properties or function arguments that might be explicitly set to
null
orundefined
.
- You want to provide a default value only if the variable is
-
Use
||
(logical OR) when:- You genuinely want to treat any falsy value as a reason to use the default value. This is less common in modern TypeScript development, but might be appropriate in specific situations where you know that any falsy value indicates an invalid or missing input.
- You want to perform a logical OR check, not related to assigning a value.
4. Chaining and Operator Precedence
Both ??
and ||
can be chained, but be mindful of operator precedence. ??
has lower precedence than ||
. It’s generally good practice to use parentheses to clarify your intent when chaining multiple operators, especially when mixing ??
and ||
.
Example (Chaining):
“`typescript
let a: string | null | undefined;
let b: string | null = null;
let c = “Default”;
let result = a ?? b ?? c; // result will be “Default”
a = “Hello”;
result = a ?? b ?? c; // result will be “Hello”
b = “World”;
result = a ?? b ?? c; // result will be “Hello” (a takes precedence)
result = a || b ?? c; // result will be “Hello” (|| has higher precedence than ??)
result = (a || b) ?? c; // result will be “Hello”
result = a || (b ?? c) // result will be “Hello”
“`
5. TypeScript’s Type Safety
TypeScript’s type system interacts beautifully with both ??
and ||
. It helps prevent errors by ensuring that the types of the operands are compatible.
“`typescript
let x: number | null = null;
let y: number = 10;
let z: string = “Hello”;
let result1 = x ?? y; // result1 is of type number (TypeScript infers the correct type)
// let result2 = x ?? z; // Type Error! string is not assignable to number | null
let result3 = x || y;
``
result1
TypeScript understands that, the right hand side is a number, it knowsmust also be a number. It will raise a type error if you try to use
??or
||` with incompatible types.
Conclusion
The nullish coalescing operator (??
) is a valuable addition to TypeScript, providing a more precise way to handle default values compared to the traditional logical OR operator (||
). By understanding the subtle but crucial difference between these two operators, you can write cleaner, more predictable, and less error-prone code. Choose ??
when you want to handle specifically null
and undefined
, and use ||
only when you genuinely want to consider all falsy values as triggers for the default value. Always remember to leverage TypeScript’s type system to catch potential errors early in your development process.