+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 15 of 355

โšก Function Types and Parameters: Mastering TypeScript Functions

Learn to create type-safe functions in TypeScript with proper parameter typing, return types, overloads, and advanced patterns ๐Ÿš€

๐ŸŒฑBeginner
25 min read

Prerequisites

  • Basic TypeScript types knowledge ๐Ÿ“
  • JavaScript functions understanding โšก
  • Arrow functions familiarity ๐Ÿ’ป

What you'll learn

  • Type function parameters and returns ๐ŸŽฏ
  • Master optional and default parameters ๐Ÿ—๏ธ
  • Understand function overloading ๐Ÿ”
  • Apply advanced function patterns โœจ

๐ŸŽฏ Introduction

Welcome to the electrifying world of TypeScript functions! โšก In this guide, weโ€™ll explore how to supercharge your functions with type safety, making them predictable, self-documenting, and bug-resistant.

Think of function types as contracts ๐Ÿ“œ - they promise what goes in and what comes out. Just like a vending machine ๐Ÿช that takes coins and gives snacks, TypeScript functions clearly state what they accept and what they return. No surprises, no mysteries!

By the end of this tutorial, youโ€™ll be writing functions that are so well-typed, they practically debug themselves! Letโ€™s power up! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Function Types

๐Ÿค” What Are Function Types?

Function types in TypeScript are like detailed instruction manuals ๐Ÿ“– for your functions. They specify:

  • โœจ What parameters the function accepts
  • ๐Ÿš€ What type each parameter should be
  • ๐Ÿ›ก๏ธ What the function returns
  • ๐Ÿ“– Whether parameters are optional

๐Ÿ’ก Why Type Functions?

Typing functions gives you superpowers:

// ๐ŸŽฏ Without types - mysterious and error-prone
function calculate(a, b, operation) {
  // What are a and b? Numbers? Strings? Objects?
  // What operations are valid?
  // What does this return?
}

// โœจ With types - clear and safe!
function calculate(a: number, b: number, operation: 'add' | 'multiply'): number {
  // Crystal clear: numbers in, number out!
  return operation === 'add' ? a + b : a * b;
}

๐Ÿ”ง Basic Function Typing

๐Ÿ“ Parameter and Return Types

Letโ€™s start with the fundamentals:

// ๐ŸŽจ Basic function with types
function greet(name: string): string {
  return `Hello, ${name}! ๐Ÿ‘‹`;
}

// ๐Ÿš€ Arrow function with types
const multiply = (x: number, y: number): number => {
  return x * y;
};

// ๐Ÿ’ก TypeScript can infer return types
const add = (a: number, b: number) => a + b; // Return type inferred as number

// ๐ŸŽฏ Void functions (no return value)
function logMessage(message: string): void {
  console.log(`๐Ÿ“ข ${message}`);
  // No return statement needed
}

// ๐Ÿ›’ Real-world example
interface Product {
  id: string;
  name: string;
  price: number;
}

function calculateDiscount(product: Product, discountPercent: number): number {
  const discount = product.price * (discountPercent / 100);
  return product.price - discount;
}

const laptop: Product = { id: "1", name: "TypeScript Laptop", price: 999 };
const finalPrice = calculateDiscount(laptop, 20); // $799.20
console.log(`Final price: $${finalPrice} ๐Ÿ’ฐ`);

๐ŸŽฎ Optional and Default Parameters

Not all parameters are required:

// ๐ŸŽฏ Optional parameters with ?
function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return `${firstName} ${lastName}`;
  }
  return firstName;
}

console.log(buildName("John")); // "John"
console.log(buildName("John", "Doe")); // "John Doe"

// โœจ Default parameters
function createGreeting(
  name: string,
  greeting: string = "Hello",
  emoji: string = "๐Ÿ‘‹"
): string {
  return `${greeting}, ${name}! ${emoji}`;
}

console.log(createGreeting("Alice")); // "Hello, Alice! ๐Ÿ‘‹"
console.log(createGreeting("Bob", "Hi")); // "Hi, Bob! ๐Ÿ‘‹"
console.log(createGreeting("Charlie", "Hey", "๐ŸŽ‰")); // "Hey, Charlie! ๐ŸŽ‰"

// ๐Ÿ—๏ธ Real-world configuration function
interface ServerConfig {
  host: string;
  port: number;
  ssl: boolean;
  timeout: number;
}

function createServer(
  host: string,
  port: number = 3000,
  ssl: boolean = false,
  timeout: number = 30000
): ServerConfig {
  return { host, port, ssl, timeout };
}

// Various ways to call it
const localServer = createServer("localhost");
const prodServer = createServer("api.example.com", 443, true);
const customServer = createServer("custom.com", 8080, false, 60000);

๐Ÿš€ Rest Parameters

Handle variable numbers of arguments:

// ๐ŸŽฏ Rest parameters with spread operator
function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40, 50)); // 150

// ๐ŸŽจ Combining regular and rest parameters
function formatMessage(template: string, ...values: (string | number)[]): string {
  let result = template;
  values.forEach((value, index) => {
    result = result.replace(`{${index}}`, String(value));
  });
  return result;
}

console.log(formatMessage("Hello {0}, you have {1} messages! ๐Ÿ“ฌ", "Alice", 5));
// "Hello Alice, you have 5 messages! ๐Ÿ“ฌ"

// ๐Ÿ›’ Shopping cart with variable items
interface CartItem {
  name: string;
  price: number;
  emoji: string;
}

function addToCart(userId: string, ...items: CartItem[]): void {
  console.log(`๐Ÿ›’ Adding ${items.length} items for user ${userId}:`);
  items.forEach(item => {
    console.log(`  ${item.emoji} ${item.name} - $${item.price}`);
  });
  
  const total = items.reduce((sum, item) => sum + item.price, 0);
  console.log(`๐Ÿ’ฐ Total: $${total.toFixed(2)}`);
}

addToCart("user123",
  { name: "TypeScript Book", price: 39.99, emoji: "๐Ÿ“˜" },
  { name: "Coffee", price: 4.99, emoji: "โ˜•" },
  { name: "Mechanical Keyboard", price: 149.99, emoji: "โŒจ๏ธ" }
);

๐Ÿ’ก Function Type Expressions

๐ŸŽฏ Defining Function Types

Create reusable function type definitions:

// ๐ŸŽจ Function type expression
type MathOperation = (x: number, y: number) => number;

const add: MathOperation = (x, y) => x + y;
const subtract: MathOperation = (x, y) => x - y;
const multiply: MathOperation = (x, y) => x * y;
const divide: MathOperation = (x, y) => y !== 0 ? x / y : 0;

// ๐ŸŽฎ Event handler types
type ClickHandler = (event: MouseEvent) => void;
type KeyHandler = (event: KeyboardEvent) => void;

const handleClick: ClickHandler = (event) => {
  console.log(`Clicked at (${event.clientX}, ${event.clientY}) ๐Ÿ–ฑ๏ธ`);
};

// ๐Ÿš€ Generic function types
type Transformer<T, R> = (input: T) => R;

const numberToString: Transformer<number, string> = (n) => n.toString();
const stringToNumber: Transformer<string, number> = (s) => parseInt(s, 10);

// ๐Ÿ—๏ธ Async function types
type AsyncValidator<T> = (value: T) => Promise<boolean>;

const validateEmail: AsyncValidator<string> = async (email) => {
  // Simulate API call
  await new Promise(resolve => setTimeout(resolve, 100));
  return email.includes('@') && email.includes('.');
};

// ๐Ÿ“ฆ Callback types
type Callback<T> = (error: Error | null, result?: T) => void;

function fetchData<T>(url: string, callback: Callback<T>): void {
  // Simulated async operation
  setTimeout(() => {
    if (url.startsWith('http')) {
      callback(null, { data: 'Success!' } as T);
    } else {
      callback(new Error('Invalid URL'));
    }
  }, 1000);
}

๐Ÿ—๏ธ Function Types in Interfaces

Embed function types in interfaces:

// ๐ŸŽฏ Interface with function properties
interface Calculator {
  add: (a: number, b: number) => number;
  subtract: (a: number, b: number) => number;
  multiply: (a: number, b: number) => number;
  divide: (a: number, b: number) => number;
  history: string[];
  lastResult: number;
}

const calculator: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => b !== 0 ? a / b : 0,
  history: [],
  lastResult: 0
};

// ๐ŸŽฎ Game controller interface
interface GameController {
  // Method signatures
  movePlayer(direction: 'up' | 'down' | 'left' | 'right'): void;
  jump(): void;
  attack(): void;
  
  // Event handlers
  onHealthChange: (newHealth: number) => void;
  onScoreUpdate: (score: number) => void;
  
  // Properties
  playerName: string;
  isConnected: boolean;
}

class PlayerController implements GameController {
  playerName = "TypeScript Hero";
  isConnected = true;
  
  movePlayer(direction: 'up' | 'down' | 'left' | 'right'): void {
    console.log(`๐Ÿƒ Moving ${direction}!`);
  }
  
  jump(): void {
    console.log("๐Ÿฆ˜ Jumping!");
  }
  
  attack(): void {
    console.log("โš”๏ธ Attacking!");
  }
  
  onHealthChange = (newHealth: number) => {
    console.log(`โค๏ธ Health: ${newHealth}`);
  };
  
  onScoreUpdate = (score: number) => {
    console.log(`๐ŸŽฏ Score: ${score}`);
  };
}

๐Ÿš€ Advanced Function Patterns

๐ŸŽฏ Function Overloading

Define multiple function signatures:

// ๐ŸŽจ Function overloads
function formatValue(value: number): string;
function formatValue(value: Date): string;
function formatValue(value: boolean): string;
function formatValue(value: number | Date | boolean): string {
  if (typeof value === 'number') {
    return `$${value.toFixed(2)}`;
  } else if (value instanceof Date) {
    return value.toLocaleDateString();
  } else {
    return value ? "Yes โœ…" : "No โŒ";
  }
}

console.log(formatValue(99.99)); // "$99.99"
console.log(formatValue(new Date())); // "12/25/2023"
console.log(formatValue(true)); // "Yes โœ…"

// ๐Ÿ—๏ธ Real-world example: Query builder
interface QueryOptions {
  limit?: number;
  offset?: number;
  orderBy?: string;
}

function query(table: string): string;
function query(table: string, id: number): string;
function query(table: string, options: QueryOptions): string;
function query(table: string, idOrOptions?: number | QueryOptions): string {
  if (typeof idOrOptions === 'number') {
    return `SELECT * FROM ${table} WHERE id = ${idOrOptions}`;
  } else if (idOrOptions) {
    const { limit, offset, orderBy } = idOrOptions;
    let sql = `SELECT * FROM ${table}`;
    if (orderBy) sql += ` ORDER BY ${orderBy}`;
    if (limit) sql += ` LIMIT ${limit}`;
    if (offset) sql += ` OFFSET ${offset}`;
    return sql;
  }
  return `SELECT * FROM ${table}`;
}

console.log(query("users")); // "SELECT * FROM users"
console.log(query("users", 123)); // "SELECT * FROM users WHERE id = 123"
console.log(query("users", { limit: 10, orderBy: "name" })); 
// "SELECT * FROM users ORDER BY name LIMIT 10"

๐Ÿง™โ€โ™‚๏ธ Generic Functions

Create flexible, reusable functions:

// ๐ŸŽฏ Basic generic function
function identity<T>(value: T): T {
  return value;
}

const num = identity<number>(42); // Type: number
const str = identity<string>("Hello"); // Type: string
const auto = identity("TypeScript"); // Type inferred as string

// ๐ŸŽจ Generic array functions
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

function last<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

function shuffle<T>(array: T[]): T[] {
  const shuffled = [...array];
  for (let i = shuffled.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  return shuffled;
}

// ๐Ÿ—๏ธ Generic with constraints
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(item: T): T {
  console.log(`Length: ${item.length}`);
  return item;
}

logLength("Hello"); // Works - strings have length
logLength([1, 2, 3]); // Works - arrays have length
// logLength(123); // Error - numbers don't have length

// ๐Ÿš€ Real-world generic: API wrapper
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function fetchApi<T>(endpoint: string): Promise<ApiResponse<T>> {
  const response = await fetch(`/api/${endpoint}`);
  const data = await response.json();
  
  return {
    data: data as T,
    status: response.status,
    message: response.statusText
  };
}

// Usage with different types
interface User {
  id: string;
  name: string;
  email: string;
}

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

const userResponse = await fetchApi<User>('users/123');
const productResponse = await fetchApi<Product[]>('products');

๐ŸŽฎ Higher-Order Functions

Functions that work with other functions:

// ๐ŸŽฏ Function that returns a function
function createMultiplier(factor: number): (value: number) => number {
  return (value: number) => value * factor;
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

// ๐Ÿ—๏ธ Function decorator pattern
type AsyncFunction<T> = (...args: any[]) => Promise<T>;

function withRetry<T>(
  fn: AsyncFunction<T>,
  maxRetries: number = 3
): AsyncFunction<T> {
  return async (...args: any[]): Promise<T> => {
    let lastError: Error | undefined;
    
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn(...args);
        
      } catch (error) {
        lastError = error as Error;
        console.log(`Attempt ${i + 1} failed, retrying... ๐Ÿ”„`);
        await new Promise(resolve => setTimeout(resolve, 1000 * i));
      }
    }
    
    throw lastError || new Error('All retries failed');
  };
}

// ๐ŸŽจ Memoization decorator
function memoize<T extends (...args: any[]) => any>(fn: T): T {
  const cache = new Map<string, ReturnType<T>>();
  
  return ((...args: Parameters<T>) => {
    const key = JSON.stringify(args);
    
    if (cache.has(key)) {
      console.log('๐Ÿ“ฆ Returning cached result');
      return cache.get(key)!;
    }
    
    const result = fn(...args);
    cache.set(key, result);
    return result;
  }) as T;
}

// Usage
const expensiveCalculation = memoize((n: number): number => {
  console.log('๐Ÿงฎ Calculating...');
  return n * n * n;
});

console.log(expensiveCalculation(5)); // Calculates
console.log(expensiveCalculation(5)); // Returns cached

// ๐Ÿš€ Composition function
function compose<T>(...functions: Array<(arg: T) => T>): (arg: T) => T {
  return (arg: T) => functions.reduceRight((acc, fn) => fn(acc), arg);
}

const addOne = (n: number) => n + 1;
const double = (n: number) => n * 2;
const square = (n: number) => n * n;

const compute = compose(square, double, addOne);
console.log(compute(3)); // ((3 + 1) * 2)ยฒ = 64

๐Ÿš€ Practical Examples

๐Ÿ›’ E-Commerce Cart System

Building a type-safe shopping cart:

// ๐Ÿ›๏ธ E-commerce function types
type PriceCalculator = (price: number, quantity: number) => number;
type DiscountStrategy = (subtotal: number) => number;
type TaxCalculator = (subtotal: number, location: string) => number;

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

interface CartItem extends Product {
  quantity: number;
}

class ShoppingCart {
  private items: Map<string, CartItem> = new Map();
  private discountStrategy?: DiscountStrategy;
  
  // โž• Add item with validation
  addItem(
    product: Product,
    quantity: number = 1,
    priceCalculator: PriceCalculator = (price, qty) => price * qty
  ): void {
    if (quantity <= 0) {
      throw new Error('Quantity must be positive');
    }
    
    const existingItem = this.items.get(product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      this.items.set(product.id, { ...product, quantity });
    }
    
    const itemTotal = priceCalculator(product.price, quantity);
    console.log(`๐Ÿ›’ Added ${quantity}x ${product.name} ($${itemTotal.toFixed(2)})`);
  }
  
  // ๐ŸŽฏ Apply discount strategy
  applyDiscount(strategy: DiscountStrategy): void {
    this.discountStrategy = strategy;
    console.log('๐Ÿ’ธ Discount applied!');
  }
  
  // ๐Ÿ’ฐ Calculate total with optional tax
  calculateTotal(taxCalculator?: TaxCalculator, location: string = 'US'): {
    subtotal: number;
    discount: number;
    tax: number;
    total: number;
  } {
    // Calculate subtotal
    const subtotal = Array.from(this.items.values()).reduce(
      (sum, item) => sum + (item.price * item.quantity),
      0
    );
    
    // Apply discount
    const discount = this.discountStrategy ? this.discountStrategy(subtotal) : 0;
    const discountedSubtotal = subtotal - discount;
    
    // Calculate tax
    const tax = taxCalculator ? taxCalculator(discountedSubtotal, location) : 0;
    
    // Final total
    const total = discountedSubtotal + tax;
    
    return { subtotal, discount, tax, total };
  }
  
  // ๐Ÿ“‹ Process checkout with callbacks
  checkout(
    onSuccess: (orderId: string) => void,
    onError: (error: Error) => void,
    paymentProcessor: (amount: number) => Promise<string>
  ): void {
    const { total } = this.calculateTotal();
    
    paymentProcessor(total)
      .then(orderId => {
        console.log('โœ… Payment successful!');
        this.items.clear();
        onSuccess(orderId);
      })
      .catch(error => {
        console.error('โŒ Payment failed:', error);
        onError(error);
      });
  }
}

// ๐ŸŽฏ Usage with different strategies
const cart = new ShoppingCart();

// Products
const laptop: Product = { id: '1', name: 'TypeScript Laptop', price: 999, category: 'electronics' };
const book: Product = { id: '2', name: 'TypeScript Handbook', price: 39.99, category: 'books' };

// Add items
cart.addItem(laptop, 1);
cart.addItem(book, 2);

// Discount strategies
const percentageDiscount: DiscountStrategy = (subtotal) => subtotal * 0.1; // 10% off
const fixedDiscount: DiscountStrategy = (subtotal) => Math.min(50, subtotal); // $50 off
const tieredDiscount: DiscountStrategy = (subtotal) => {
  if (subtotal > 1000) return subtotal * 0.15;
  if (subtotal > 500) return subtotal * 0.1;
  return subtotal * 0.05;
};

// Apply discount
cart.applyDiscount(tieredDiscount);

// Tax calculators
const usTaxCalculator: TaxCalculator = (subtotal, state) => {
  const taxRates: Record<string, number> = {
    'CA': 0.0725,
    'NY': 0.08,
    'TX': 0.0625,
    'FL': 0.06
  };
  return subtotal * (taxRates[state] || 0.05);
};

// Calculate total
const totals = cart.calculateTotal(usTaxCalculator, 'CA');
console.log('๐Ÿ’ฐ Order Summary:', totals);

// Mock payment processor
const mockPaymentProcessor = async (amount: number): Promise<string> => {
  await new Promise(resolve => setTimeout(resolve, 1000));
  if (Math.random() > 0.1) { // 90% success rate
    return `ORDER-${Date.now()}`;
  }
  throw new Error('Payment declined');
};

// Checkout
cart.checkout(
  (orderId) => console.log(`๐ŸŽ‰ Order placed: ${orderId}`),
  (error) => console.log(`๐Ÿ˜ž Checkout failed: ${error.message}`),
  mockPaymentProcessor
);

๐ŸŽฎ Game Development Functions

Building a game system with typed functions:

// ๐ŸŽฎ Game function types
type DamageCalculator = (attacker: Character, defender: Character, skill: Skill) => number;
type StatusEffect = (target: Character, duration: number) => void;
type AIStrategy = (character: Character, enemies: Character[]) => Action;

interface Character {
  id: string;
  name: string;
  health: number;
  maxHealth: number;
  attack: number;
  defense: number;
  speed: number;
  skills: Skill[];
}

interface Skill {
  name: string;
  damage: number;
  manaCost: number;
  cooldown: number;
  effect?: StatusEffect;
}

type Action = 
  | { type: 'attack'; targetId: string; skillName: string }
  | { type: 'defend' }
  | { type: 'heal' }
  | { type: 'flee' };

class BattleSystem {
  private characters: Map<string, Character> = new Map();
  private turnOrder: string[] = [];
  private currentTurn: number = 0;
  
  // โš”๏ธ Execute action with custom damage calculation
  executeAction(
    actorId: string,
    action: Action,
    damageCalculator: DamageCalculator = this.defaultDamageCalculator
  ): void {
    const actor = this.characters.get(actorId);
    if (!actor) return;
    
    switch (action.type) {
      case 'attack': {
        const target = this.characters.get(action.targetId);
        const skill = actor.skills.find(s => s.name === action.skillName);
        
        if (target && skill) {
          const damage = damageCalculator(actor, target, skill);
          target.health = Math.max(0, target.health - damage);
          
          console.log(`โš”๏ธ ${actor.name} uses ${skill.name}! ${damage} damage to ${target.name}`);
          
          if (skill.effect) {
            skill.effect(target, 3); // 3 turn duration
          }
        }
        break;
      }
      
      case 'defend': {
        console.log(`๐Ÿ›ก๏ธ ${actor.name} defends!`);
        actor.defense *= 1.5; // Temporary defense boost
        break;
      }
      
      case 'heal': {
        const healAmount = Math.floor(actor.maxHealth * 0.3);
        actor.health = Math.min(actor.maxHealth, actor.health + healAmount);
        console.log(`๐Ÿ’š ${actor.name} heals for ${healAmount} HP!`);
        break;
      }
      
      case 'flee': {
        console.log(`๐Ÿƒ ${actor.name} flees from battle!`);
        this.removeCharacter(actorId);
        break;
      }
    }
  }
  
  // ๐ŸŽฏ Default damage calculator
  private defaultDamageCalculator: DamageCalculator = (attacker, defender, skill) => {
    const baseDamage = skill.damage + attacker.attack;
    const defense = defender.defense;
    const variance = 0.8 + Math.random() * 0.4; // 80-120% damage
    
    return Math.floor(Math.max(1, (baseDamage - defense) * variance));
  };
  
  // ๐Ÿง  AI turn with strategy
  aiTurn(
    characterId: string,
    strategy: AIStrategy = this.aggressiveStrategy
  ): void {
    const character = this.characters.get(characterId);
    if (!character) return;
    
    const enemies = Array.from(this.characters.values())
      .filter(c => c.id !== characterId);
    
    const action = strategy(character, enemies);
    this.executeAction(characterId, action);
  }
  
  // ๐ŸŽฎ AI Strategies
  private aggressiveStrategy: AIStrategy = (character, enemies) => {
    const target = enemies.reduce((weakest, enemy) => 
      enemy.health < weakest.health ? enemy : weakest
    );
    
    const strongestSkill = character.skills.reduce((best, skill) =>
      skill.damage > best.damage ? skill : best
    );
    
    return {
      type: 'attack',
      targetId: target.id,
      skillName: strongestSkill.name
    };
  };
  
  private defensiveStrategy: AIStrategy = (character, enemies) => {
    if (character.health < character.maxHealth * 0.3) {
      return { type: 'heal' };
    }
    
    if (character.health < character.maxHealth * 0.5) {
      return { type: 'defend' };
    }
    
    // Default to attacking weakest enemy
    return this.aggressiveStrategy(character, enemies);
  };
  
  // ๐Ÿ”„ Turn order based on speed
  initializeTurnOrder(
    speedModifier: (character: Character) => number = (c) => c.speed
  ): void {
    const characters = Array.from(this.characters.values());
    
    this.turnOrder = characters
      .sort((a, b) => speedModifier(b) - speedModifier(a))
      .map(c => c.id);
    
    console.log('๐ŸŽฏ Turn order:', this.turnOrder.map(id => 
      this.characters.get(id)?.name
    ).join(' โ†’ '));
  }
  
  // โž• Helper methods
  addCharacter(character: Character): void {
    this.characters.set(character.id, character);
  }
  
  removeCharacter(id: string): void {
    this.characters.delete(id);
    this.turnOrder = this.turnOrder.filter(tid => tid !== id);
  }
}

// ๐ŸŽฏ Status effect functions
const burnEffect: StatusEffect = (target, duration) => {
  console.log(`๐Ÿ”ฅ ${target.name} is burning for ${duration} turns!`);
  // In real implementation, would apply damage over time
};

const freezeEffect: StatusEffect = (target, duration) => {
  console.log(`โ„๏ธ ${target.name} is frozen for ${duration} turns!`);
  target.speed *= 0.5; // Reduce speed
};

// ๐ŸŽฎ Usage example
const battle = new BattleSystem();

// Create characters
const hero: Character = {
  id: 'hero',
  name: 'TypeScript Knight',
  health: 100,
  maxHealth: 100,
  attack: 20,
  defense: 15,
  speed: 10,
  skills: [
    { name: 'Slash', damage: 25, manaCost: 0, cooldown: 0 },
    { name: 'Fire Strike', damage: 35, manaCost: 10, cooldown: 2, effect: burnEffect }
  ]
};

const enemy: Character = {
  id: 'enemy',
  name: 'Bug Monster',
  health: 80,
  maxHealth: 80,
  attack: 15,
  defense: 10,
  speed: 12,
  skills: [
    { name: 'Bite', damage: 20, manaCost: 0, cooldown: 0 },
    { name: 'Ice Breath', damage: 30, manaCost: 15, cooldown: 3, effect: freezeEffect }
  ]
};

// Setup battle
battle.addCharacter(hero);
battle.addCharacter(enemy);
battle.initializeTurnOrder();

// Execute turns
battle.executeAction('hero', { 
  type: 'attack', 
  targetId: 'enemy', 
  skillName: 'Fire Strike' 
});

battle.aiTurn('enemy', battle['defensiveStrategy']);

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Optional vs Undefined Parameters

// โŒ Confusing optional and undefined
function greet(name?: string, greeting: string | undefined) {
  // name can be omitted, greeting must be passed (but can be undefined)
}

greet(); // Error! greeting is required
greet("Alice", undefined); // OK

// โœ… Clear intent
function betterGreet(name: string, greeting?: string) {
  const message = greeting || "Hello";
  return `${message}, ${name}!`;
}

betterGreet("Alice"); // "Hello, Alice!"
betterGreet("Bob", "Hi"); // "Hi, Bob!"

๐Ÿคฏ Pitfall 2: Type Inference Issues

// โŒ Poor type inference
const numbers = []; // Type: never[]
numbers.push(1); // Error!

// โœ… Explicit typing
const numbers: number[] = [];
numbers.push(1); // Works!

// โŒ Losing type information
function processValue(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value * 2;
}
const result = processValue("hello"); // Type: string | number (not specific!)

// โœ… Function overloads for better types
function betterProcess(value: string): string;
function betterProcess(value: number): number;
function betterProcess(value: string | number): string | number {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value * 2;
}

const stringResult = betterProcess("hello"); // Type: string
const numberResult = betterProcess(42); // Type: number

๐Ÿ˜ต Pitfall 3: this Context Issues

// โŒ Lost context
class Counter {
  count = 0;
  
  increment() {
    this.count++;
  }
}

const counter = new Counter();
const incrementFn = counter.increment;
incrementFn(); // Error! 'this' is undefined

// โœ… Arrow functions preserve context
class BetterCounter {
  count = 0;
  
  increment = () => {
    this.count++;
  }
}

// โœ… Or use bind
const boundIncrement = counter.increment.bind(counter);

// โœ… Or specify this type
interface ClickableElement {
  addEventListener(event: string, handler: (this: HTMLElement, e: Event) => void): void;
}

๐Ÿ› ๏ธ Best Practices

๐ŸŽฏ Function Type Best Practices

  1. ๐Ÿ“ Use Descriptive Parameter Names: Make intent clear

    // โŒ Vague names
    function calc(x: number, y: number, z: boolean): number
    
    // โœ… Descriptive names
    function calculatePrice(
      basePrice: number,
      taxRate: number,
      includeShipping: boolean
    ): number
  2. ๐Ÿ—๏ธ Prefer Interface for Complex Parameters: Better readability

    // โŒ Too many parameters
    function createUser(
      name: string,
      email: string,
      age: number,
      country: string,
      newsletter: boolean
    ): User
    
    // โœ… Options object
    interface CreateUserOptions {
      name: string;
      email: string;
      age: number;
      country: string;
      newsletter: boolean;
    }
    
    function createUser(options: CreateUserOptions): User
  3. ๐ŸŽจ Use Function Types for Callbacks: Reusable and clear

    type SuccessCallback<T> = (data: T) => void;
    type ErrorCallback = (error: Error) => void;
    
    function fetchData<T>(
      url: string,
      onSuccess: SuccessCallback<T>,
      onError: ErrorCallback
    ): void
  4. โœจ Return Early for Guard Clauses: Cleaner flow

    function processPayment(amount: number, cardNumber?: string): boolean {
      if (!cardNumber) {
        console.error("No card number provided");
        return false;
      }
      
      if (amount <= 0) {
        console.error("Invalid amount");
        return false;
      }
      
      // Main logic here
      return true;
    }
  5. ๐Ÿš€ Use Generics for Flexibility: Type-safe and reusable

    function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
      const result = {} as Pick<T, K>;
      keys.forEach(key => {
        result[key] = obj[key];
      });
      return result;
    }

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Type-Safe Event System

Create a flexible event system with typed functions:

๐Ÿ“‹ Requirements:

  • โœ… Type-safe event emitter and listeners
  • ๐Ÿท๏ธ Different event types with specific payloads
  • ๐Ÿ‘ฅ Support for multiple listeners per event
  • ๐Ÿ“… Event history with timestamps
  • ๐ŸŽจ Filtering and middleware support

๐Ÿš€ Bonus Points:

  • Add once listeners
  • Implement event priority
  • Create async event handlers

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Type-safe event system
type EventMap = {
  login: { userId: string; timestamp: Date };
  logout: { userId: string; reason?: string };
  purchase: { userId: string; amount: number; items: string[] };
  error: { message: string; code: number; stack?: string };
};

type EventKey = keyof EventMap;
type EventHandler<K extends EventKey> = (payload: EventMap[K]) => void | Promise<void>;
type Middleware<K extends EventKey> = (payload: EventMap[K], next: () => void) => void;

interface EventRecord<K extends EventKey = EventKey> {
  type: K;
  payload: EventMap[K];
  timestamp: Date;
}

class TypedEventEmitter {
  private handlers: Map<EventKey, Set<EventHandler<any>>> = new Map();
  private onceHandlers: Map<EventKey, Set<EventHandler<any>>> = new Map();
  private middleware: Map<EventKey, Middleware<any>[]> = new Map();
  private history: EventRecord[] = [];
  private maxHistorySize: number = 100;
  
  // ๐Ÿ“ Register event handler
  on<K extends EventKey>(event: K, handler: EventHandler<K>): () => void {
    if (!this.handlers.has(event)) {
      this.handlers.set(event, new Set());
    }
    
    this.handlers.get(event)!.add(handler);
    console.log(`๐Ÿ“Œ Registered handler for '${event}'`);
    
    // Return unsubscribe function
    return () => {
      this.handlers.get(event)?.delete(handler);
      console.log(`๐Ÿ“Œ Unregistered handler for '${event}'`);
    };
  }
  
  // ๐ŸŽฏ Register one-time handler
  once<K extends EventKey>(event: K, handler: EventHandler<K>): void {
    if (!this.onceHandlers.has(event)) {
      this.onceHandlers.set(event, new Set());
    }
    
    this.onceHandlers.get(event)!.add(handler);
  }
  
  // ๐Ÿ”„ Add middleware
  use<K extends EventKey>(event: K, middleware: Middleware<K>): void {
    if (!this.middleware.has(event)) {
      this.middleware.set(event, []);
    }
    
    this.middleware.get(event)!.push(middleware);
    console.log(`๐Ÿ”„ Added middleware for '${event}'`);
  }
  
  // ๐Ÿš€ Emit event
  async emit<K extends EventKey>(event: K, payload: EventMap[K]): Promise<void> {
    console.log(`๐Ÿ“ข Emitting '${event}' event`);
    
    // Add to history
    this.addToHistory({ type: event, payload, timestamp: new Date() });
    
    // Execute middleware chain
    const middlewares = this.middleware.get(event) || [];
    let middlewareIndex = 0;
    
    const next = () => {
      if (middlewareIndex < middlewares.length) {
        const middleware = middlewares[middlewareIndex++];
        middleware(payload, next);
      } else {
        // Execute handlers after middleware
        this.executeHandlers(event, payload);
      }
    };
    
    next();
  }
  
  // ๐ŸŽฏ Execute handlers
  private async executeHandlers<K extends EventKey>(
    event: K,
    payload: EventMap[K]
  ): Promise<void> {
    // Regular handlers
    const handlers = this.handlers.get(event) || new Set();
    const promises: Promise<void>[] = [];
    
    for (const handler of handlers) {
      const result = handler(payload);
      if (result instanceof Promise) {
        promises.push(result);
      }
    }
    
    // Once handlers
    const onceHandlers = this.onceHandlers.get(event) || new Set();
    for (const handler of onceHandlers) {
      const result = handler(payload);
      if (result instanceof Promise) {
        promises.push(result);
      }
    }
    
    // Clear once handlers
    this.onceHandlers.delete(event);
    
    // Wait for all async handlers
    if (promises.length > 0) {
      await Promise.all(promises);
    }
  }
  
  // ๐Ÿ“Š Event history management
  private addToHistory<K extends EventKey>(record: EventRecord<K>): void {
    this.history.push(record);
    
    // Trim history if too large
    if (this.history.length > this.maxHistorySize) {
      this.history = this.history.slice(-this.maxHistorySize);
    }
  }
  
  // ๐Ÿ” Query history
  getHistory<K extends EventKey>(
    eventType?: K,
    filter?: (record: EventRecord<K>) => boolean
  ): EventRecord<K>[] {
    let results = this.history;
    
    if (eventType) {
      results = results.filter(record => record.type === eventType);
    }
    
    if (filter) {
      results = results.filter(record => filter(record as EventRecord<K>));
    }
    
    return results as EventRecord<K>[];
  }
  
  // ๐Ÿ“Š Get event statistics
  getStats(): Record<EventKey, { count: number; lastEmitted?: Date }> {
    const stats = {} as Record<EventKey, { count: number; lastEmitted?: Date }>;
    
    for (const record of this.history) {
      if (!stats[record.type]) {
        stats[record.type] = { count: 0 };
      }
      
      stats[record.type].count++;
      stats[record.type].lastEmitted = record.timestamp;
    }
    
    return stats;
  }
  
  // ๐Ÿงน Clear all handlers
  clear(event?: EventKey): void {
    if (event) {
      this.handlers.delete(event);
      this.onceHandlers.delete(event);
      this.middleware.delete(event);
    } else {
      this.handlers.clear();
      this.onceHandlers.clear();
      this.middleware.clear();
    }
  }
}

// ๐ŸŽฎ Usage example
const eventSystem = new TypedEventEmitter();

// Register handlers
eventSystem.on('login', async ({ userId, timestamp }) => {
  console.log(`โœ… User ${userId} logged in at ${timestamp.toLocaleTimeString()}`);
  
  // Simulate async operation
  await new Promise(resolve => setTimeout(resolve, 100));
  console.log(`๐Ÿ“ง Welcome email sent to ${userId}`);
});

eventSystem.on('purchase', ({ userId, amount, items }) => {
  console.log(`๐Ÿ’ฐ User ${userId} purchased ${items.length} items for $${amount}`);
  console.log(`๐Ÿ“ฆ Items: ${items.join(', ')}`);
});

// One-time handler
eventSystem.once('error', ({ message, code }) => {
  console.log(`๐Ÿšจ First error: ${message} (Code: ${code})`);
});

// Middleware for logging
eventSystem.use('purchase', (payload, next) => {
  console.log('๐Ÿ“ Logging purchase...', payload);
  next();
});

// Middleware for validation
eventSystem.use('purchase', (payload, next) => {
  if (payload.amount < 0) {
    console.error('โŒ Invalid purchase amount!');
    return; // Don't call next() to stop the event
  }
  next();
});

// Emit events
await eventSystem.emit('login', {
  userId: 'user123',
  timestamp: new Date()
});

await eventSystem.emit('purchase', {
  userId: 'user123',
  amount: 99.99,
  items: ['TypeScript Book', 'Coffee Mug']
});

await eventSystem.emit('error', {
  message: 'Connection timeout',
  code: 408
});

// This won't trigger the once handler
await eventSystem.emit('error', {
  message: 'Another error',
  code: 500
});

// Query history
const purchaseHistory = eventSystem.getHistory('purchase');
console.log(`\n๐Ÿ“Š Purchase history: ${purchaseHistory.length} events`);

const recentErrors = eventSystem.getHistory('error', record => 
  record.timestamp > new Date(Date.now() - 60000) // Last minute
);

// Get statistics
const stats = eventSystem.getStats();
console.log('\n๐Ÿ“ˆ Event statistics:', stats);

// ๐ŸŽฏ Advanced: Priority event system
class PriorityEventEmitter extends TypedEventEmitter {
  private priorities: Map<EventKey, Map<EventHandler<any>, number>> = new Map();
  
  // Override to add priority
  onWithPriority<K extends EventKey>(
    event: K,
    handler: EventHandler<K>,
    priority: number = 0
  ): () => void {
    const unsubscribe = this.on(event, handler);
    
    if (!this.priorities.has(event)) {
      this.priorities.set(event, new Map());
    }
    
    this.priorities.get(event)!.set(handler, priority);
    
    return () => {
      unsubscribe();
      this.priorities.get(event)?.delete(handler);
    };
  }
  
  // Override to sort by priority
  protected async executeHandlers<K extends EventKey>(
    event: K,
    payload: EventMap[K]
  ): Promise<void> {
    const handlers = Array.from(this.handlers.get(event) || []);
    const priorities = this.priorities.get(event) || new Map();
    
    // Sort by priority (higher first)
    handlers.sort((a, b) => {
      const priorityA = priorities.get(a) || 0;
      const priorityB = priorities.get(b) || 0;
      return priorityB - priorityA;
    });
    
    // Execute in priority order
    for (const handler of handlers) {
      await handler(payload);
    }
  }
}

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered function types and parameters in TypeScript! Hereโ€™s what you can now do:

  • โœ… Type function parameters with confidence ๐Ÿ’ช
  • โœ… Define return types for predictable functions ๐ŸŽฏ
  • โœ… Use optional and rest parameters effectively ๐Ÿ—๏ธ
  • โœ… Create function overloads for flexibility ๐Ÿ”
  • โœ… Build type-safe APIs with advanced patterns! ๐Ÿš€

Remember: Well-typed functions are self-documenting and catch bugs before they happen. Type everything! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve become a function typing master!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Complete the event system exercise
  2. ๐Ÿ—๏ธ Add types to existing JavaScript functions
  3. ๐Ÿ“š Learn about generics and conditional types
  4. ๐ŸŒŸ Explore async function patterns!

Remember: Great functions start with great types. Keep building! ๐Ÿš€

Happy coding! ๐ŸŽ‰๐Ÿš€โœจ