TypeScript ?? vs || : Understanding the Difference

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
function greet(name: string | null) {
const displayName = name || "Guest";
console.log(
Hello, ${displayName}!`);
}

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 not undefined), it returns the left-hand side value.
  • If the left-hand side is nullish (either null or undefined), 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
function greet(name: string | null) {
const displayName = name ?? "Guest";
console.log(
Hello, ${displayName}!`);
}

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 or undefined.
    • 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 or undefined.
  • 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;
``
TypeScript understands that, the right hand side is a number, it knows
result1must 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top