Skip to Content
Course content

142: Optional Properties Revisited

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably used the ? modifier a hundred times by now, but there's a subtle trap that still trips me up when I'm rushing through a PR: the gap between a property being optional in the type system and how it actually behaves at runtime.

interface UserProfile {
  firstName: string;
  lastName: string;
  middleName?: string; // Optional!
}

function getFullDisplayName(user: UserProfile) {
  // I'm just building a simple string here...
  return `${user.firstName} ${user.middleName} ${user.lastName}`;
}

const userA = { firstName: "Jane", lastName: "Doe" };
console.log(getFullDisplayName(userA)); 
// Output: "Jane undefined Doe"

The "undefined" string leak

If you're using a strict TypeScript configuration, the compiler might actually let this slide depending on your noImplicitAny and strictNullChecks settings, but the runtime result is a disaster. JavaScript doesn't "skip" the property just because it's missing; it evaluates the missing property as undefined and then happily coerces that into the string "undefined".

I've seen this happen in production logs more times than I'd like to admit. The developer thinks, "It's optional, so if it's not there, it just isn't there." But in the eyes of a template literal, undefined is a value that needs to be represented.

Cleaning up with nullish coalescing and filtering

The fix isn't just about silencing a warning; it's about deciding what the "fallback" behavior should be. If you just want to omit the middle name when it's missing, you can't just rely on the ? in the interface. You have to handle the absence of the value explicitly.

function getFullDisplayName(user: UserProfile) {
  // Option 1: The quick fix with nullish coalescing
  // This still leaves an extra space if middleName is missing.
  // return `${user.firstName} ${user.middleName ?? ''} ${user.lastName}`;

  // Option 2: The professional approach
  // Filter out the falsy/undefined values and join them with a space.
  return [user.firstName, user.middleName, user.lastName]
    .filter(Boolean) 
    .join(' ');
}

const userA = { firstName: "Jane", lastName: "Doe" };
console.log(getFullDisplayName(userA)); // Output: "Jane Doe"

I prefer the array approach here. Why? Because it scales. If you suddenly decide to add prefix (like "Dr.") or suffix (like "III") as optional properties, you don't have to rewrite a complex chain of ternary operators or worry about double-spaces appearing in your UI. You just add them to the array.

One quick side note: Be careful using .filter(Boolean) if 0 or false are valid values you want to keep. In the case of names, it's perfect. In the case of a score?: number property, Boolean(0) is false, and your zero would vanish. In those cases, use a more explicit check: .filter(val => val !== undefined).




📋 Practical Task

Refactoring the Product Discount Calculator

You are working on an e-commerce checkout system. The Product interface has several optional properties. Currently, the calculateFinalPrice function is producing NaN or weird results because it doesn't properly handle the optional properties during math operations.

Your Task: Fix the calculateFinalPrice function so that it handles missing discount and taxRate values without crashing or returning NaN. Assume a default tax rate of 0 if none is provided, and a discount of 0 if none is provided.

interface Product {
  name: string;
  basePrice: number;
  discount?: number; // Percentage, e.g., 0.1 for 10%
  taxRate?: number;  // Percentage, e.g., 0.05 for 5%
}

function calculateFinalPrice(product: Product): string {
  // BUGGY CODE:
  const priceAfterDiscount = product.basePrice * (1 - product.discount);
  const finalPrice = priceAfterDiscount * (1 + product.taxRate);
  
  return `Total: $${finalPrice.toFixed(2)}`;
}

// Test Case 1: Should be $100.00
const item1: Product = { name: "Plain T-Shirt", basePrice: 100 }; 

// Test Case 2: Should be $94.50 (100 - 10% discount + 5% tax on 90)
const item2: Product = { 
  name: "Fancy T-Shirt", 
  basePrice: 100, 
  discount: 0.1, 
  taxRate: 0.05 
};

console.log(calculateFinalPrice(item1));
console.log(calculateFinalPrice(item2));
Rating
0 0

There are no comments for now.

to be the first to leave a comment.