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
| Concept | Syntax | Example |
|---|---|---|
| Parameter types | (x: Type) | (price: number) |
| Return type | : Type | : number |
| Optional param | param? | description?: string |
| Default param | param = value | quantity = 1 |
| Void return | : void | Side effects only |
| Function type | (params) => Return | (n: number) => string |
Next up: Union Types - handling multiple types like payment methods and order statuses!