+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 77 of 355

🔧 Parameters and ReturnType: Function Type Utilities

Master TypeScript's Parameters and ReturnType utility types to extract function signatures, build type-safe wrappers, and create powerful functional abstractions 🚀

🚀Intermediate
28 min read

Prerequisites

  • Understanding TypeScript function types and signatures 📝
  • Familiarity with utility types (Pick, Omit, NonNullable) ⚡
  • Basic knowledge of generic types and constraints 💻

What you'll learn

  • Master Parameters<T> for extracting function parameter types 🎯
  • Use ReturnType<T> to capture function return types 🏗️
  • Build type-safe function wrappers and decorators 🛡️
  • Apply function utilities in real-world architectures ✨

🎯 Introduction

Welcome to this comprehensive guide on TypeScript’s Parameters and ReturnType utility types! 🎉 These powerful tools are like X-ray machines for functions - they let you peek inside function signatures and extract their type information with surgical precision.

You’ll discover how Parameters and ReturnType can transform how you work with functions, enabling you to build type-safe wrappers, decorators, and functional abstractions. Whether you’re creating middleware systems 🔄, API clients 🌐, or testing utilities 🧪, these utility types provide the foundation for robust, type-safe function manipulation.

By the end of this tutorial, you’ll be extracting and manipulating function types like a TypeScript function surgeon! Let’s dive in! 🏊‍♂️

📚 Understanding Parameters and ReturnType

🤔 What are Parameters and ReturnType?

Parameters and ReturnType are like function signature detectives 🔍. Think of Parameters as a tool that extracts the input arguments from a function signature, while ReturnType captures what the function gives back.

In TypeScript terms:

  • Parameters<T> 📥: Extracts parameter types from function T as a tuple
  • ReturnType<T> 📤: Extracts the return type from function T
  • Both work with any function type, including methods and constructors

This means you can:

  • ✨ Extract parameter types without redefining them
  • 🚀 Build type-safe wrappers that match existing functions
  • 🛡️ Create decorators with perfect type preservation
  • 🔄 Transform functions while maintaining type safety

💡 Why Use Parameters and ReturnType?

Here’s why developers love these utility types:

  1. Type Consistency 🎯: Ensure wrapper functions match original signatures
  2. DRY Principle 🔄: Don’t repeat function type definitions
  3. Type Safety 🛡️: Maintain compile-time guarantees in function manipulation
  4. Flexibility 🌟: Build generic utilities that adapt to any function

Real-world example: Imagine building a logging decorator 📝. You want to wrap any function while preserving its exact signature. Parameters and ReturnType make this seamless!

🔧 Parameters - Extracting Function Arguments

📝 Basic Parameters Syntax

Let’s start with the fundamentals:

// 🏗️ Sample functions with different signatures
function greetUser(name: string, age: number): string {
  return `Hello ${name}, you are ${age} years old! 👋`;
}

function calculateTotal(items: number[], tax: number, discount?: number): number {
  const subtotal = items.reduce((sum, item) => sum + item, 0);
  const taxAmount = subtotal * tax;
  const discountAmount = (discount || 0) * subtotal;
  return subtotal + taxAmount - discountAmount;
}

// 🎯 Extract parameter types
type GreetParams = Parameters<typeof greetUser>;        // [string, number]
type CalcParams = Parameters<typeof calculateTotal>;    // [number[], number, number?]

// ✅ Usage examples
const greetArgs: GreetParams = ['Alice', 25];          // ✅ Valid
const calcArgs: CalcParams = [[10, 20, 30], 0.08, 0.1]; // ✅ Valid

// ❌ Type errors caught at compile time
// const badGreet: GreetParams = ['Alice'];             // ❌ Missing age
// const badCalc: CalcParams = [[], 'invalid'];         // ❌ Wrong types

🎯 Accessing Individual Parameters

Parameters returns a tuple, so you can access individual parameter types:

// 🎮 Game function with multiple parameters
function createCharacter(
  name: string, 
  level: number, 
  class: 'warrior' | 'mage' | 'archer',
  equipment: { weapon: string; armor: string },
  isNPC: boolean = false
): GameCharacter {
  return {
    name,
    level,
    class,
    equipment,
    isNPC,
    id: Math.random().toString(36)
  };
}

// 📋 Extract all parameters
type CreateCharacterParams = Parameters<typeof createCharacter>;
// [string, number, 'warrior' | 'mage' | 'archer', { weapon: string; armor: string }, boolean?]

// 🎯 Access individual parameter types
type CharacterName = CreateCharacterParams[0];      // string
type CharacterLevel = CreateCharacterParams[1];     // number
type CharacterClass = CreateCharacterParams[2];     // 'warrior' | 'mage' | 'archer'
type CharacterEquipment = CreateCharacterParams[3]; // { weapon: string; armor: string }
type IsNPC = CreateCharacterParams[4];              // boolean | undefined

// ✨ Usage in related functions
const validateCharacterName = (name: CharacterName): boolean => {
  return name.length> 0 && name.length <= 50;
};

const getClassBonuses = (characterClass: CharacterClass) => {
  switch (characterClass) {
    case 'warrior': return { strength: 5, defense: 3 };
    case 'mage': return { intelligence: 5, mana: 10 };
    case 'archer': return { dexterity: 5, speed: 3 };
  }
};

📤 ReturnType - Capturing Function Results

📝 Basic ReturnType Syntax

Now let’s explore ReturnType:

// 🛒 E-commerce functions with different return types
function fetchUserProfile(userId: string): Promise<{
  id: string;
  name: string;
  email: string;
  preferences: UserPreferences;
}> {
  return fetch(`/api/users/${userId}`).then(res => res.json());
}

function calculateShipping(weight: number, distance: number): {
  cost: number;
  estimatedDays: number;
  carrier: string;
} {
  return {
    cost: weight * 0.1 + distance * 0.05,
    estimatedDays: Math.ceil(distance / 100),
    carrier: 'Standard'
  };
}

// 🎯 Extract return types
type UserProfile = ReturnType<typeof fetchUserProfile>;    // Promise<{...}>
type ShippingInfo = ReturnType<typeof calculateShipping>;  // { cost: number; ... }

// ✨ Use extracted types in other functions
const displayProfile = async (profile: UserProfile) => {
  const user = await profile; // 👤 TypeScript knows this is the user object
  console.log(`User: ${user.name} (${user.email})`);
};

const formatShipping = (shipping: ShippingInfo): string => {
  return `Shipping: $${shipping.cost} (${shipping.estimatedDays} days via ${shipping.carrier})`;
};

🔄 Unwrapping Promise Return Types

Working with async functions and Promises:

// 🌐 API functions returning promises
async function fetchProducts(category: string): Promise<Product[]> {
  const response = await fetch(`/api/products?category=${category}`);
  return response.json();
}

async function createOrder(orderData: OrderData): Promise<{
  orderId: string;
  status: 'pending' | 'confirmed';
  total: number;
  estimatedDelivery: Date;
}> {
  const response = await fetch('/api/orders', {
    method: 'POST',
    body: JSON.stringify(orderData)
  });
  return response.json();
}

// 📦 Extract Promise return types
type ProductsPromise = ReturnType<typeof fetchProducts>;     // Promise<Product[]>
type OrderPromise = ReturnType<typeof createOrder>;         // Promise<{...}>

// 🎯 Extract the actual resolved types
type ProductArray = Awaited<ProductsPromise>;               // Product[]
type OrderResult = Awaited<OrderPromise>;                   // { orderId: string; ... }

// ✨ Use in related functions
const processProducts = (products: ProductArray) => {
  // 🛍️ TypeScript knows this is Product[]
  return products.filter(product => product.inStock);
};

const handleOrderSuccess = (order: OrderResult) => {
  // 📋 TypeScript knows the exact structure
  console.log(`Order ${order.orderId} created with status: ${order.status}`);
  console.log(`Total: $${order.total}, Delivery: ${order.estimatedDelivery}`);
};

🚀 Practical Examples and Patterns

🔄 Function Wrappers and Decorators

// 🕐 Timing decorator that preserves function signatures
function withTiming<T extends (...args: any[]) => any>(
  fn: T,
  label: string
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args: Parameters<T>): ReturnType<T> => {
    const start = performance.now();
    const result = fn(...args);
    const end = performance.now();
    console.log(`⏱️ ${label} took ${end - start}ms`);
    return result;
  };
}

// 📊 Sample functions to wrap
function expensiveCalculation(data: number[], iterations: number): number {
  let result = 0;
  for (let i = 0; i < iterations; i++) {
    result += data.reduce((sum, num) => sum + Math.sqrt(num), 0);
  }
  return result;
}

async function fetchUserData(id: string): Promise<UserData> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// ✨ Wrap functions while preserving types
const timedCalculation = withTiming(expensiveCalculation, 'Expensive Calculation');
const timedFetch = withTiming(fetchUserData, 'User Data Fetch');

// 🎯 Function signatures are perfectly preserved!
const result1 = timedCalculation([1, 2, 3, 4], 1000);    // number
const result2 = await timedFetch('user123');             // UserData

// 🛡️ Type safety maintained
// timedCalculation('invalid', 'args');                  // ❌ Error!
// timedFetch(123);                                      // ❌ Error!

📝 Logging Middleware System

// 🗃️ Logging middleware that preserves function types
type LogLevel = 'info' | 'warn' | 'error' | 'debug';

function withLogging<T extends (...args: any[]) => any>(
  fn: T,
  options: {
    level: LogLevel;
    logArgs?: boolean;
    logResult?: boolean;
    context?: string;
  }
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args: Parameters<T>): ReturnType<T> => {
    const { level, logArgs, logResult, context } = options;
    const fnName = fn.name || 'anonymous';
    const prefix = context ? `[${context}]` : '';
    
    // 📥 Log function call
    console.log(`${prefix} Calling ${fnName} (${level})`);
    
    // 📝 Log arguments if requested
    if (logArgs) {
      console.log(`${prefix} Arguments:`, args);
    }
    
    try {
      const result = fn(...args);
      
      // 📤 Log result if requested
      if (logResult) {
        console.log(`${prefix} Result:`, result);
      }
      
      return result;
    } catch (error) {
      console.error(`${prefix} Error in ${fnName}:`, error);
      throw error;
    }
  };
}

// 🏪 Business logic functions
function processPayment(amount: number, currency: string, cardToken: string): {
  transactionId: string;
  status: 'success' | 'failed';
  message: string;
} {
  // Payment processing logic
  return {
    transactionId: `txn_${Date.now()}`,
    status: 'success',
    message: 'Payment processed successfully'
  };
}

function sendNotification(userId: string, type: 'email' | 'sms', message: string): Promise<boolean> {
  // Notification sending logic
  return Promise.resolve(true);
}

// ✨ Create logged versions
const loggedPayment = withLogging(processPayment, {
  level: 'info',
  logArgs: true,
  logResult: true,
  context: 'PAYMENT'
});

const loggedNotification = withLogging(sendNotification, {
  level: 'debug',
  logArgs: false,
  logResult: true,
  context: 'NOTIFICATION'
});

// 🎯 Perfect type preservation
const paymentResult = loggedPayment(99.99, 'USD', 'card_token123');
// Type: { transactionId: string; status: 'success' | 'failed'; message: string; }

const notificationPromise = loggedNotification('user123', 'email', 'Welcome!');
// Type: Promise<boolean>

🔧 Type-Safe Function Composition

// 🏗️ Function composition utilities
type AnyFunction = (...args: any[]) => any;

// 🔗 Compose two functions with type safety
function compose<T extends AnyFunction, U extends AnyFunction>(
  f: T,
  g: U
): (...args: Parameters<U>) => ReturnType<T> {
  return (...args: Parameters<U>): ReturnType<T> => {
    return f(g(...args));
  };
}

// 📊 Sample transformation functions
function parseNumber(str: string): number {
  const parsed = parseFloat(str);
  if (isNaN(parsed)) {
    throw new Error(`Invalid number: ${str}`);
  }
  return parsed;
}

function doubleValue(num: number): number {
  return num * 2;
}

function formatCurrency(num: number): string {
  return `$${num.toFixed(2)}`;
}

// ✨ Compose functions with perfect type inference
const parseAndDouble = compose(doubleValue, parseNumber);
// Type: (str: string) => number

const parseDoubleFormat = compose(formatCurrency, parseAndDouble);
// Type: (str: string) => string

// 🎯 Usage with full type safety
const result1 = parseAndDouble('42');      // number (84)
const result2 = parseDoubleFormat('25.5'); // string ("$51.00")

// ❌ Type errors prevent mistakes
// parseAndDouble(42);                    // ❌ Error: expects string
// parseDoubleFormat(100);                // ❌ Error: expects string

🧪 Testing Utilities with Type Extraction

// 🧪 Type-safe testing utilities
interface MockConfig<T extends AnyFunction> {
  returnValue?: ReturnType<T>;
  implementation?: T;
  callCount?: number;
}

class MockFunction<T extends AnyFunction> {
  private calls: Parameters<T>[] = [];
  private config: MockConfig<T>;

  constructor(config: MockConfig<T> = {}) {
    this.config = config;
  }

  // 🎯 Mock function with exact signature preservation
  getMock(): (...args: Parameters<T>) => ReturnType<T> {
    return (...args: Parameters<T>): ReturnType<T> => {
      this.calls.push(args);
      
      if (this.config.implementation) {
        return this.config.implementation(...args);
      }
      
      return this.config.returnValue as ReturnType<T>;
    };
  }

  // 📊 Testing utilities
  getCallCount(): number {
    return this.calls.length;
  }

  getLastCall(): Parameters<T> | undefined {
    return this.calls[this.calls.length - 1];
  }

  getAllCalls(): Parameters<T>[] {
    return [...this.calls];
  }

  wasCalledWith(...args: Parameters<T>): boolean {
    return this.calls.some(call => 
      call.length === args.length && 
      call.every((arg, index) => arg === args[index])
    );
  }
}

// 📧 Function to test
function sendEmail(to: string, subject: string, body: string): Promise<{
  messageId: string;
  status: 'sent' | 'failed';
}> {
  // Email sending logic
  return Promise.resolve({
    messageId: `msg_${Date.now()}`,
    status: 'sent'
  });
}

// ✨ Create type-safe mock
const emailMock = new MockFunction<typeof sendEmail>({
  returnValue: Promise.resolve({
    messageId: 'test_msg_123',
    status: 'sent' as const
  })
});

const mockSendEmail = emailMock.getMock();

// 🧪 Test usage
await mockSendEmail('[email protected]', 'Test Subject', 'Test Body');

// 📋 Verify mock interactions
console.log('Call count:', emailMock.getCallCount());           // 1
console.log('Last call:', emailMock.getLastCall());            // ['[email protected]', 'Test Subject', 'Test Body']
console.log('Called with specific args:', 
  emailMock.wasCalledWith('[email protected]', 'Test Subject', 'Test Body')); // true

💡 Advanced Patterns and Combinations

🔄 Function Type Transformation

// 🎯 Transform function signatures
type MakeAsync<T extends (...args: any[]) => any> = (
  ...args: Parameters<T>
) => Promise<ReturnType<T>>;

type MakeOptional<T extends (...args: any[]) => any> = (
  ...args: Partial<Parameters<T>>
) => ReturnType<T>;

// 📊 Original synchronous functions
function calculateTax(amount: number, rate: number): number {
  return amount * rate;
}

function formatUserName(firstName: string, lastName: string): string {
  return `${firstName} ${lastName}`;
}

// ✨ Transform to async versions
const asyncCalculateTax: MakeAsync<typeof calculateTax> = async (amount, rate) => {
  // Simulate async tax calculation (e.g., API call)
  await new Promise(resolve => setTimeout(resolve, 100));
  return amount * rate;
};

// 🔄 Transform to optional parameter versions
const optionalFormatName: MakeOptional<typeof formatUserName> = (firstName?, lastName?) => {
  return `${firstName || 'Unknown'} ${lastName || 'User'}`;
};

// 🎯 Type-safe usage
const tax = await asyncCalculateTax(100, 0.08);        // Promise<number>
const name = optionalFormatName('John');               // string

🏗️ Generic Function Builders

// 🏭 Factory for creating typed event handlers
type EventHandler<T> = (event: T) => void;

class TypedEventEmitter<TEvents extends Record<string, any>> {
  private handlers: Partial<{
    [K in keyof TEvents]: EventHandler<TEvents[K]>[]
  }> = {};

  // 📢 Type-safe event registration
  on<K extends keyof TEvents>(
    event: K, 
    handler: EventHandler<TEvents[K]>
  ): void {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event]!.push(handler);
  }

  // 🚀 Type-safe event emission
  emit<K extends keyof TEvents>(
    event: K, 
    data: TEvents[K]
  ): void {
    const eventHandlers = this.handlers[event];
    if (eventHandlers) {
      eventHandlers.forEach(handler => handler(data));
    }
  }

  // 🔧 Create type-safe handler wrapper
  createHandler<K extends keyof TEvents>(
    event: K,
    fn: (data: TEvents[K]) => void
  ): EventHandler<TEvents[K]> {
    return (data: TEvents[K]) => {
      console.log(`📡 Handling event: ${String(event)}`);
      fn(data);
    };
  }
}

// 🎮 Game event system
interface GameEvents {
  playerMove: { x: number; y: number; playerId: string };
  itemCollected: { itemId: string; playerId: string; value: number };
  levelComplete: { level: number; score: number; time: number };
}

const gameEvents = new TypedEventEmitter<GameEvents>();

// ✨ Type-safe event handlers
const moveHandler = gameEvents.createHandler('playerMove', (data) => {
  // 🎯 TypeScript knows data is { x: number; y: number; playerId: string }
  console.log(`Player ${data.playerId} moved to (${data.x}, ${data.y})`);
});

const itemHandler = gameEvents.createHandler('itemCollected', (data) => {
  // 🎯 TypeScript knows data is { itemId: string; playerId: string; value: number }
  console.log(`Player ${data.playerId} collected item ${data.itemId} worth ${data.value}`);
});

// 📋 Register handlers
gameEvents.on('playerMove', moveHandler);
gameEvents.on('itemCollected', itemHandler);

// 🚀 Emit events with type safety
gameEvents.emit('playerMove', { x: 10, y: 5, playerId: 'player1' });
gameEvents.emit('itemCollected', { itemId: 'sword', playerId: 'player1', value: 100 });

🔧 Advanced Function Analysis

// 🔍 Function signature analyzer
type FunctionInfo<T extends (...args: any[]) => any> = {
  parameters: Parameters<T>;
  returnType: ReturnType<T>;
  arity: Parameters<T>['length'];
  isAsync: ReturnType<T> extends Promise<any> ? true : false;
};

// 🛠️ Function introspection utility
function analyzeFunctionType<T extends (...args: any[]) => any>(
  fn: T
): FunctionInfo<T> {
  // Note: This is a type-level analysis, runtime arity detection would be different
  return {} as FunctionInfo<T>;
}

// 📊 Sample functions for analysis
function syncFunction(a: string, b: number): boolean {
  return a.length> b;
}

async function asyncFunction(id: string): Promise<UserData> {
  return { id, name: 'User', email: '[email protected]' };
}

function variadicFunction(...args: string[]): string {
  return args.join(' ');
}

// 🎯 Type-level function analysis
type SyncInfo = FunctionInfo<typeof syncFunction>;
// {
//   parameters: [string, number];
//   returnType: boolean;
//   arity: 2;
//   isAsync: false;
// }

type AsyncInfo = FunctionInfo<typeof asyncFunction>;
// {
//   parameters: [string];
//   returnType: Promise<UserData>;
//   arity: 1;
//   isAsync: true;
// }

type VariadicInfo = FunctionInfo<typeof variadicFunction>;
// {
//   parameters: string[];
//   returnType: string;
//   arity: number;
//   isAsync: false;
// }

⚠️ Common Pitfalls and Solutions

❌ Wrong: Losing Type Information

// ❌ BAD: Manual parameter extraction loses type safety
interface UserService {
  createUser(name: string, email: string, age: number): Promise<User>;
}

// ❌ Manually redefining parameters - prone to drift
function createUserWrapper(name: string, email: string, age: number) {
  // 😱 What happens if the original function signature changes?
  return userService.createUser(name, email, age);
}

✅ Right: Using Parameters for Type Safety

// ✅ GOOD: Extract parameters automatically
interface UserService {
  createUser(name: string, email: string, age: number): Promise<User>;
}

// ✅ Parameters extraction stays in sync
function createUserWrapper(
  ...args: Parameters<UserService['createUser']>
): ReturnType<UserService['createUser']> {
  console.log('Creating user with args:', args);
  return userService.createUser(...args);
}

// 🎯 Type safety maintained automatically
const user = createUserWrapper('John', '[email protected]', 30); // ✅ Correct
// createUserWrapper('John', 30, '[email protected]');           // ❌ Error: wrong order

❌ Wrong: Ignoring Async Return Types

// ❌ BAD: Not handling Promise return types properly
async function fetchData(id: string): Promise<DataResponse> {
  return await api.get(`/data/${id}`);
}

// ❌ Wrong: treating Promise as direct value
function processData(data: ReturnType<typeof fetchData>) {
  // 😱 data is Promise<DataResponse>, not DataResponse!
  console.log(data.results); // ❌ Property 'results' doesn't exist on Promise
}

✅ Right: Proper Promise Handling

// ✅ GOOD: Use Awaited for Promise unwrapping
async function fetchData(id: string): Promise<DataResponse> {
  return await api.get(`/data/${id}`);
}

// ✅ Correct: unwrap Promise type
function processData(data: Awaited<ReturnType<typeof fetchData>>) {
  // 🎯 data is DataResponse, properly unwrapped
  console.log(data.results); // ✅ Works correctly
}

// ✅ Or handle Promise directly
async function processDataAsync(dataPromise: ReturnType<typeof fetchData>) {
  const data = await dataPromise; // 🎯 Explicit awaiting
  console.log(data.results); // ✅ Works correctly
}

🛠️ Best Practices

1. 🎯 Use with Generic Constraints

// ✅ EXCELLENT: Constrain generics for better type safety
type AsyncFunction = (...args: any[]) => Promise<any>;
type SyncFunction = (...args: any[]) => any;

function withRetry<T extends AsyncFunction>(
  fn: T,
  maxAttempts: number = 3
): (...args: Parameters<T>) => ReturnType<T> {
  return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => {
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await fn(...args);
      } catch (error) {
        if (attempt === maxAttempts) throw error;
        console.log(`Attempt ${attempt} failed, retrying...`);
      }
    }
    throw new Error('All attempts failed');
  };
}

2. 🏗️ Build Reusable Function Utilities

// 🧱 Reusable function transformation utilities
type FunctionTransformers = {
  // 🕐 Add timing to any function
  withTiming<T extends (...args: any[]) => any>(
    fn: T
  ): (...args: Parameters<T>) => ReturnType<T>;
  
  // 🔄 Add retry logic to async functions
  withRetry<T extends (...args: any[]) => Promise<any>>(
    fn: T, 
    attempts: number
  ): (...args: Parameters<T>) => ReturnType<T>;
  
  // 📝 Add logging to any function
  withLogging<T extends (...args: any[]) => any>(
    fn: T,
    level: LogLevel
  ): (...args: Parameters<T>) => ReturnType<T>;
  
  // 🛡️ Add error handling to any function
  withErrorHandling<T extends (...args: any[]) => any>(
    fn: T,
    fallback: ReturnType<T>
  ): (...args: Parameters<T>) => ReturnType<T>;
};

3. 📝 Document Function Transformations

// ✨ Well-documented function transformers
/**
 * Creates a memoized version of the provided function.
 * 
 * @template T - The function type to memoize
 * @param fn - The function to memoize
 * @param keyGenerator - Optional function to generate cache keys
 * @returns Memoized function with same signature as input
 */
function memoize<T extends (...args: any[]) => any>(
  fn: T,
  keyGenerator?: (...args: Parameters<T>) => string
): (...args: Parameters<T>) => ReturnType<T> {
  const cache = new Map<string, ReturnType<T>>();
  
  return (...args: Parameters<T>): ReturnType<T> => {
    const key = keyGenerator ? keyGenerator(...args) : JSON.stringify(args);
    
    if (cache.has(key)) {
      return cache.get(key)!;
    }
    
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

🧪 Hands-On Exercise

Let’s build a comprehensive function middleware system! 🔧

📋 Step 1: Define Middleware Types

// 🔧 Middleware system types
type Middleware<T extends (...args: any[]) => any> = {
  before?: (...args: Parameters<T>) => void | Promise<void>;
  after?: (result: ReturnType<T>, ...args: Parameters<T>) => void | Promise<void>;
  onError?: (error: Error, ...args: Parameters<T>) => void | Promise<void>;
  transform?: (result: ReturnType<T>) => ReturnType<T>;
};

interface MiddlewareConfig {
  name: string;
  enabled: boolean;
  priority: number;
}

type MiddlewareWithConfig<T extends (...args: any[]) => any> = {
  middleware: Middleware<T>;
  config: MiddlewareConfig;
};

🎯 Step 2: Implement Middleware Engine

// 🏗️ Middleware engine with perfect type preservation
class FunctionMiddlewareEngine {
  static applyMiddleware<T extends (...args: any[]) => any>(
    originalFn: T,
    middlewares: MiddlewareWithConfig<T>[]
  ): (...args: Parameters<T>) => ReturnType<T> {
    
    // 📊 Sort middlewares by priority
    const sortedMiddlewares = middlewares
      .filter(m => m.config.enabled)
      .sort((a, b) => b.config.priority - a.config.priority);

    return async (...args: Parameters<T>): Promise<Awaited<ReturnType<T>>> => {
      // 🚀 Execute before middlewares
      for (const { middleware, config } of sortedMiddlewares) {
        if (middleware.before) {
          console.log(`🔄 [${config.name}] Before middleware`);
          await middleware.before(...args);
        }
      }

      try {
        // 🎯 Execute original function
        let result = await originalFn(...args);

        // 🔄 Apply transformations
        for (const { middleware, config } of sortedMiddlewares) {
          if (middleware.transform) {
            console.log(`🔧 [${config.name}] Transforming result`);
            result = middleware.transform(result);
          }
        }

        // ✨ Execute after middlewares
        for (const { middleware, config } of sortedMiddlewares.reverse()) {
          if (middleware.after) {
            console.log(`✅ [${config.name}] After middleware`);
            await middleware.after(result, ...args);
          }
        }

        return result;
      } catch (error) {
        // 🚫 Execute error middlewares
        for (const { middleware, config } of sortedMiddlewares) {
          if (middleware.onError) {
            console.log(`❌ [${config.name}] Error middleware`);
            await middleware.onError(error as Error, ...args);
          }
        }
        throw error;
      }
    };
  }
}

🚀 Step 3: Create Business Functions and Middlewares

// 💼 Business functions to enhance
async function processOrder(
  customerId: string, 
  items: OrderItem[], 
  paymentMethod: string
): Promise<{
  orderId: string;
  total: number;
  status: 'pending' | 'confirmed';
  estimatedDelivery: Date;
}> {
  // Simulate order processing
  await new Promise(resolve => setTimeout(resolve, 100));
  
  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  
  return {
    orderId: `order_${Date.now()}`,
    total,
    status: 'confirmed',
    estimatedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
  };
}

function calculateDiscount(
  customerTier: 'bronze' | 'silver' | 'gold',
  orderTotal: number
): {
  discountPercent: number;
  discountAmount: number;
  finalTotal: number;
} {
  const discountRates = { bronze: 0.05, silver: 0.1, gold: 0.15 };
  const discountPercent = discountRates[customerTier];
  const discountAmount = orderTotal * discountPercent;
  
  return {
    discountPercent,
    discountAmount,
    finalTotal: orderTotal - discountAmount
  };
}

// 🔧 Create middleware for processOrder
const orderMiddlewares: MiddlewareWithConfig<typeof processOrder>[] = [
  {
    middleware: {
      before: async (customerId, items, paymentMethod) => {
        console.log(`📋 Validating order for customer: ${customerId}`);
        console.log(`💳 Payment method: ${paymentMethod}`);
        console.log(`🛒 Items: ${items.length}`);
      },
      after: async (result, customerId) => {
        console.log(`✅ Order ${result.orderId} processed successfully`);
        console.log(`💰 Total: $${result.total}`);
        // Send confirmation email
        await sendOrderConfirmation(customerId, result.orderId);
      },
      onError: async (error, customerId) => {
        console.error(`❌ Order processing failed for customer ${customerId}:`, error);
        // Log to error tracking service
        await logError('ORDER_PROCESSING', error, { customerId });
      }
    },
    config: { name: 'Order Validation', enabled: true, priority: 100 }
  },
  {
    middleware: {
      before: async () => {
        console.log('⏱️ Starting order processing timer');
      },
      after: async (result) => {
        console.log('⏱️ Order processing completed');
        // Track processing time
        await trackMetric('order_processing_time', performance.now());
      }
    },
    config: { name: 'Performance Tracking', enabled: true, priority: 50 }
  }
];

// 🔧 Create middleware for calculateDiscount
const discountMiddlewares: MiddlewareWithConfig<typeof calculateDiscount>[] = [
  {
    middleware: {
      transform: (result) => {
        // Apply additional seasonal discount
        const seasonalBonus = 0.02; // 2% extra
        const extraDiscount = result.finalTotal * seasonalBonus;
        
        return {
          ...result,
          discountAmount: result.discountAmount + extraDiscount,
          finalTotal: result.finalTotal - extraDiscount
        };
      }
    },
    config: { name: 'Seasonal Discount', enabled: true, priority: 75 }
  }
];

🎯 Step 4: Apply Middleware and Test

// ✨ Create enhanced functions
const enhancedProcessOrder = FunctionMiddlewareEngine.applyMiddleware(
  processOrder,
  orderMiddlewares
);

const enhancedCalculateDiscount = FunctionMiddlewareEngine.applyMiddleware(
  calculateDiscount,
  discountMiddlewares
);

// 🧪 Test the enhanced functions
async function testMiddlewareSystem() {
  try {
    // 🛒 Test order processing
    const orderResult = await enhancedProcessOrder(
      'customer_123',
      [
        { id: 'item1', name: 'Laptop', price: 999, quantity: 1 },
        { id: 'item2', name: 'Mouse', price: 25, quantity: 2 }
      ],
      'credit_card'
    );
    
    console.log('📦 Order Result:', orderResult);
    
    // 💰 Test discount calculation
    const discountResult = enhancedCalculateDiscount('gold', 1000);
    console.log('💸 Discount Result:', discountResult);
    
  } catch (error) {
    console.error('Test failed:', error);
  }
}

// 🚀 Run the test
testMiddlewareSystem();

🎓 Challenge Solution

Amazing! 🎉 You’ve built a sophisticated middleware system using Parameters and ReturnType that:

  • Preserves exact function signatures
  • Supports multiple middleware types (before, after, error, transform)
  • Maintains full type safety throughout the pipeline
  • Allows for dynamic middleware configuration

🎓 Key Takeaways

Outstanding work! 🎉 You’ve mastered Parameters and ReturnType utility types. Here’s what you’ve learned:

🏆 Core Concepts

  • Parameters<T> 📥: Extracts function parameter types as a tuple
  • ReturnType<T> 📤: Extracts function return type
  • Both preserve exact type information from function signatures

💡 Best Practices

  • 🎯 Use with generic constraints for better type safety
  • 🏗️ Build reusable function transformation utilities
  • 📝 Document function transformations clearly
  • 🔄 Combine with other utility types for powerful patterns

🚀 Real-World Applications

  • 🔧 Function wrappers and decorators that preserve signatures
  • 🧪 Type-safe testing utilities and mocks
  • 🔄 Middleware systems with perfect type preservation
  • 🏗️ Function composition and transformation libraries

🤝 Next Steps

Ready to explore more utility types? Here are your next adventures:

  1. ConstructorParameters and InstanceType 🏗️: Work with class constructor types
  2. ThisParameterType and OmitThisParameter 🎯: Handle this context in functions
  3. Conditional Types 🤔: Build types that adapt based on conditions
  4. Template Literal Types 📝: Create powerful string manipulation types

Keep building amazing, type-safe function utilities with Parameters and ReturnType! 🚀✨

You’re becoming a TypeScript function type master! 🧙‍♂️🔧