🔥 0
0
Lesson 5 of 10 25 min +125 XP

Typed Functions

Functions are the workhorses of any app. Let's make them type-safe.

Basic Function Types

Add types to parameters and return values:

// Parameters: price is number, quantity is number
// Return: number
function calculateSubtotal(price: number, quantity: number): number {
  return price * quantity;
}

const subtotal = calculateSubtotal(29.99, 3);  // 89.97
calculateSubtotal("29.99", 3);  // ❌ Error: string not assignable to number

Arrow Functions

Same concept, different syntax:

// Inline type annotation
const calculateTax = (amount: number, rate: number): number => {
  return amount * rate;
};

// Shorter for simple functions
const formatPrice = (price: number): string => `${price.toFixed(2)}`;

console.log(formatPrice(29.99));  // "$29.99"

Type Aliases for Functions

Define reusable function types:

// Define the function type
type PriceCalculator = (price: number, quantity: number) => number;

// Use it
const calculateTotal: PriceCalculator = (price, quantity) => {
  return price * quantity;
};

const calculateWithTax: PriceCalculator = (price, quantity) => {
  return price * quantity * 1.1;  // 10% tax
};

---

Optional Parameters

Use ? for parameters that might not be provided:

function createProduct(
  name: string,
  price: number,
  description?: string  // Optional - might be undefined
): Product {
  return {
    id: generateId(),
    name,
    price,
    description,  // Will be undefined if not provided
    inStock: true
  };
}

// Both valid:
createProduct("Mouse", 29.99);
createProduct("Keyboard", 99.99, "Mechanical RGB keyboard");

Default Parameters

Provide fallback values:

function calculateShipping(
  subtotal: number,
  method: string = "standard"  // Default value
): number {
  const rates: Record<string, number> = {
    standard: 5.99,
    express: 12.99,
    overnight: 24.99
  };

  // Free shipping over $100
  if (subtotal >= 100) return 0;

  return rates[method] ?? rates.standard;
}

calculateShipping(50);            // 5.99 (uses default)
calculateShipping(50, "express"); // 12.99
calculateShipping(150);           // 0 (free shipping)

---

Functions with Objects

Type object parameters using interfaces:

interface CartItem {
  product: Product;
  quantity: number;
}

interface Cart {
  items: CartItem[];
  couponCode: string | null;
}

// Object parameter
function calculateCartTotal(cart: Cart): number {
  return cart.items.reduce(
    (sum, item) => sum + (item.product.price * item.quantity),
    0
  );
}

// Destructured parameter
function addToCart(
  cart: Cart,
  { product, quantity }: CartItem
): Cart {
  return {
    ...cart,
    items: [...cart.items, { product, quantity }]
  };
}

---

Callback Functions

Type functions passed as arguments:

interface Product {
  id: string;
  name: string;
  price: number;
  inStock: boolean;
}

// Callback type: (product: Product) => boolean
function filterProducts(
  products: Product[],
  predicate: (product: Product) => boolean
): Product[] {
  return products.filter(predicate);
}

const allProducts: Product[] = [
  { id: "1", name: "Phone", price: 699, inStock: true },
  { id: "2", name: "Tablet", price: 499, inStock: false },
  { id: "3", name: "Watch", price: 299, inStock: true }
];

// Use with different callbacks
const available = filterProducts(allProducts, p => p.inStock);
const affordable = filterProducts(allProducts, p => p.price < 500);
const phones = filterProducts(allProducts, p => p.name.includes("Phone"));

---

Void Return Type

For functions that don't return anything:

function logPurchase(orderId: string, total: number): void {
  console.log(`Order ${orderId} completed: ${total}`);
  // No return statement needed
}

// With side effects
function updateInventory(productId: string, quantity: number): void {
  // Updates database, doesn't return anything
  database.update(productId, { stock: quantity });
}

---

Never Return Type

For functions that never return (errors, infinite loops):

function throwError(message: string): never {
  throw new Error(message);
}

function handleInvalidPayment(code: string): never {
  throw new Error(`Invalid payment method: ${code}`);
}

// Useful in exhaustive checks (covered in Type Narrowing)

---

Building E-commerce Functions

Let's build a complete set of cart functions:

interface Product {
  id: string;
  name: string;
  price: number;
}

interface CartItem {
  product: Product;
  quantity: number;
}

interface Cart {
  items: CartItem[];
  couponCode: string | null;
}

// Create empty cart
function createCart(): Cart {
  return { items: [], couponCode: null };
}

// Add item to cart
function addItem(cart: Cart, product: Product, quantity: number = 1): Cart {
  const existingIndex = cart.items.findIndex(
    item => item.product.id === product.id
  );

  if (existingIndex >= 0) {
    // Update quantity
    const newItems = [...cart.items];
    newItems[existingIndex] = {
      ...newItems[existingIndex],
      quantity: newItems[existingIndex].quantity + quantity
    };
    return { ...cart, items: newItems };
  }

  // Add new item
  return {
    ...cart,
    items: [...cart.items, { product, quantity }]
  };
}

// Remove item from cart
function removeItem(cart: Cart, productId: string): Cart {
  return {
    ...cart,
    items: cart.items.filter(item => item.product.id !== productId)
  };
}

// Calculate totals
function getCartSummary(cart: Cart): {
  subtotal: number;
  itemCount: number;
  discount: number;
  total: number;
} {
  const subtotal = cart.items.reduce(
    (sum, item) => sum + item.product.price * item.quantity,
    0
  );

  const itemCount = cart.items.reduce(
    (count, item) => count + item.quantity,
    0
  );

  const discount = cart.couponCode ? subtotal * 0.1 : 0;  // 10% off with coupon

  return {
    subtotal,
    itemCount,
    discount,
    total: subtotal - discount
  };
}

// Apply coupon
function applyCoupon(cart: Cart, code: string): Cart {
  return { ...cart, couponCode: code };
}

---

Type Challenges

Challenge 1: Type the Search Function

Add types to this search function:

function searchProducts(products, query, maxResults) {
  return products
    .filter(p => p.name.toLowerCase().includes(query.toLowerCase()))
    .slice(0, maxResults);
}
Solution
function searchProducts(
  products: Product[],
  query: string,
  maxResults: number = 10
): Product[] {
  return products
    .filter(p => p.name.toLowerCase().includes(query.toLowerCase()))
    .slice(0, maxResults);
}

Challenge 2: Type the Price Formatter

Create a function type and implement it:

// Create a type for this function signature:
// Takes a number, returns a string like "$99.99"
// Optional second parameter for currency symbol (default "$")

// Your type here
type ???

// Implement it
const formatPrice: ??? = ???
Solution
type PriceFormatter = (amount: number, currency?: string) => string;

const formatPrice: PriceFormatter = (amount, currency = "$") => {
  return `${currency}${amount.toFixed(2)}`;
};

formatPrice(99.99);        // "$99.99"
formatPrice(99.99, "€");   // "€99.99"

---

Key Takeaways

ConceptSyntaxExample
Parameter types(x: Type)(price: number)
Return type: Type: number
Optional paramparam?description?: string
Default paramparam = valuequantity = 1
Void return: voidSide effects only
Function type(params) => Return(n: number) => string

Next up: Union Types - handling multiple types like payment methods and order statuses!

Arrays & Objects