When to use TypeScript's double question mark
You're validating form data where users can enter 0 as a quantity. Your fallback logic uses || to set a default, but suddenly every zero input gets replaced with your default value of 1. Users can't order zero items even when that's valid for your app. This bug happens because || treats 0 as falsy.
TypeScript's double question mark (??), or nullish coalescing operator, solves this by only falling back when values are actually null or undefined. It preserves intentional zeros, empty strings, and false values while still catching genuinely missing data. This makes it essential for working with APIs, form inputs, and configuration objects where you need precise control over default values.
What is the Nullish Coalescing Operator?
The nullish coalescing operator (??) evaluates to its right-hand operand when its left-hand operand is null or undefined; otherwise, it returns the left-hand operand. This makes it perfect for assigning default values without affecting other falsy values like empty strings or zero.
const username = user.name ?? 'Guest';
In this example, username will be set to user.name if it exists; otherwise, it will default to 'Guest'.
Using the Double Question Mark in TypeScript
The double question mark is useful in many scenarios, like when dealing with objects that might have missing properties. For instance:
const user = {
name: 'John',
address: null,
};
const username = user.name ?? 'Guest';
const userAddress = user.address ?? 'Unknown';
Here, username becomes 'John' while userAddress becomes 'Unknown' because user.address is null. typescript null coalesce operations like this help prevent errors when working with potentially missing data.
Managing Null and Undefined Values
This operator is valuable when working with objects that may have null properties. It allows you to set default values for these properties, enhancing your code's reliability.
const user = {
name: 'John',
address: null,
};
const username = user.name ?? 'Guest';
const userAddress = user.address ?? 'Unknown';
console.log(username); // John
console.log(userAddress); // Unknown
The code above demonstrates how default value assignment works with the nullish coalescing operator. When integrating with Convex data validation, this pattern helps ensure your application gracefully handles missing data.
Setting Default Values in Configuration Objects
The nullish coalescing operator shines when working with configuration objects where you need to merge user settings with defaults. Here's a practical pattern:
interface ApiConfig {
timeout?: number;
retries?: number;
verbose?: boolean;
}
function createApiClient(userConfig: ApiConfig) {
// Preserve user's intentional false or 0 values
const timeout = userConfig.timeout ?? 3000;
const retries = userConfig.retries ?? 3;
const verbose = userConfig.verbose ?? true;
return { timeout, retries, verbose };
}
// User wants zero retries and silent mode
const client = createApiClient({ retries: 0, verbose: false });
console.log(client); // { timeout: 3000, retries: 0, verbose: false }
Without ??, using || would incorrectly override the user's 0 and false values with the defaults. The nullish coalescing operator respects these intentional choices while still providing fallbacks for missing properties.
Preventing Null or Undefined Errors
The double question mark helps prevent errors by ensuring variables have fallback values if they are null or undefined.
const user = {
name: 'John',
address: null,
};
const username = user.name ?? 'Guest';
const userAddress = user.address ?? 'Unknown';
console.log(username.length); // 4
console.log(userAddress.length); // 7
This pattern is essential when working with complex data structures where certain properties might be missing. With proper fallback values in place, your code can safely access properties without throwing errors, even when data is incomplete.
Simplifying Code with Optional Chaining
Combine the double question mark with optional chaining to make your code cleaner. For instance:
const user = {
name: 'John',
address: {
street: '123 Main St',
},
};
const userStreet = user.address?.street ?? 'Unknown';
This example shows how optional chaining works alongside the nullish coalescing operator to safely access nested properties. If either user.address or user.address.street is null or undefined, userStreet will default to 'Unknown'. This technique is especially important when working with external data sources where the structure isn't always guaranteed.
Comparing with the Logical OR Operator
The ?? and || operators behave differently because they check for different conditions. The || operator returns the right value when the left is any falsy value (false, 0, '', NaN, null, undefined), while ?? only checks for null or undefined.
// OR operator treats all falsy values the same
const count1 = 0 || 10; // 10 (0 is falsy)
const enabled1 = false || true; // true (false is falsy)
const text1 = '' || 'default'; // 'default' ('' is falsy)
// Nullish coalescing only cares about null/undefined
const count2 = 0 ?? 10; // 0 (0 is not nullish)
const enabled2 = false ?? true; // false (false is not nullish)
const text2 = '' ?? 'default'; // '' ('' is not nullish)
This distinction matters when processing form inputs where empty strings, zeros, and false values represent valid user input rather than missing data.
When to Choose ?? vs ||
| Use ?? when you want to preserve | Use || when you want to replace |
|-----------------------------------|----------------------------------|
| 0 (zero quantity, zero price) | 0 with a default number |
| false (disabled checkboxes) | false with true |
| '' (empty but submitted fields) | '' with a default string |
| Checking only null/undefined | Any falsy value with a default |
Common Challenges and Solutions
Handling Null and Undefined Gracefully
Developers often face challenges with null and undefined values, which can cause errors. The nullish coalescing operator provides a clean solution by ensuring variables have fallback values only when genuinely needed.
// Without nullish coalescing
const count = data.count !== null && data.count !== undefined ? data.count : 0;
// With nullish coalescing
const count = data.count ?? 0;
This approach pairs well with Convex's type system for building robust applications.
Simplifying Conditional Assignments
The nullish coalescing operator makes default value assignments more concise while maintaining the correct behavior for empty strings, zero, and other falsy values:
// The old way
const text = options.text === null || options.text === undefined ? 'Default' : options.text;
// Using nullish coalescing
const text = options.text ?? 'Default';
This technique is valuable when implementing null check logic throughout your codebase.
Real-World API Response Handling
When working with API responses, you'll often deal with optional fields that might be null, undefined, or legitimately falsy. Here's how to handle them correctly:
interface ApiResponse {
productId: string;
price?: number;
inStock?: boolean;
description?: string;
}
function processProduct(response: ApiResponse) {
// Wrong: || replaces 0 price and false inStock with defaults
const price1 = response.price || 99.99; // Free item becomes $99.99!
const inStock1 = response.inStock || true; // Out of stock becomes available!
// Right: ?? only replaces null/undefined
const price2 = response.price ?? 99.99; // 0 stays 0 (free item)
const inStock2 = response.inStock ?? true; // false stays false
const description = response.description ?? 'No description available';
return { price: price2, inStock: inStock2, description };
}
When migrating from JavaScript to TypeScript, audit your || usage carefully. The nullish coalescing operator gives you more precise control over default values than traditional OR-based fallbacks.
Nullish Coalescing Assignment (??=)
TypeScript also supports the nullish coalescing assignment operator (??=), which only assigns a value if the current value is null or undefined. This is perfect for initializing properties that might already have values:
interface CacheEntry {
data?: string;
timestamp?: number;
hitCount?: number;
}
function updateCache(entry: CacheEntry) {
// Only set if not already present
entry.timestamp ??= Date.now();
entry.hitCount ??= 0;
// Increment hit count
entry.hitCount++;
return entry;
}
const cached = { data: 'some data' };
updateCache(cached);
console.log(cached); // { data: 'some data', timestamp: 1234567890, hitCount: 1 }
// Second call preserves existing timestamp
updateCache(cached);
console.log(cached); // { data: 'some data', timestamp: 1234567890, hitCount: 2 }
The ??= operator is shorthand for value = value ?? defaultValue, but it only evaluates the right side if needed, making it more efficient. This works great for lazy initialization and building up configuration objects from multiple sources.
TypeScript Version Support
The nullish coalescing operator (??) requires TypeScript 3.7 or later. When compiling to older JavaScript targets (before ES2020), TypeScript will transpile ?? into equivalent conditional checks. For modern projects targeting ES2020 or later, the operator compiles directly to native JavaScript with no overhead.
Final Thoughts
The nullish coalescing operator (??) gives you precise control over default values by distinguishing between "missing data" (null/undefined) and "intentional falsy values" (0, false, empty strings). Use it for configuration objects, API responses, and form data where preserving user intent matters. Combine it with optional chaining (?.) for safe property access, and reach for ??= when you need conditional initialization. These operators help you write clearer, less error-prone code compared to traditional ||-based fallbacks.