+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 16 of 355

๐ŸŽฏ Optional and Default Parameters: Flexible Function Design in TypeScript

Master optional and default parameters in TypeScript to create flexible, user-friendly functions with practical examples ๐Ÿš€

๐ŸŒฑBeginner
20 min read

Prerequisites

  • Basic TypeScript function knowledge ๐Ÿ“
  • Understanding of function parameters โšก
  • TypeScript development environment ๐Ÿ’ป

What you'll learn

  • Create functions with optional parameters ๐ŸŽฏ
  • Set default values for parameters ๐Ÿ—๏ธ
  • Combine optional and default parameters ๐Ÿ”
  • Apply best practices for flexible APIs โœจ

๐ŸŽฏ Introduction

Welcome to the wonderful world of optional and default parameters in TypeScript! ๐ŸŽ‰ In this guide, weโ€™ll explore how to create flexible, user-friendly functions that adapt to different use cases.

Imagine building a coffee ordering system โ˜• where customers might want sugar, milk, or specific sizes - but not everyone needs every option. Thatโ€™s where optional and default parameters shine! They let you create functions that work perfectly whether called with one argument or ten.

By the end of this tutorial, youโ€™ll be designing elegant APIs that your teammates will love using! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Optional and Default Parameters

๐Ÿค” What Are Optional Parameters?

Optional parameters are like extras on a pizza ๐Ÿ•. The base pizza is great on its own, but you can add toppings if you want! In TypeScript, optional parameters let callers decide whether to provide certain arguments.

Optional parameters are marked with a ? after the parameter name:

function greet(name: string, emoji?: string) {
  // emoji is optional!
}

๐Ÿ’ก What Are Default Parameters?

Default parameters are like having a favorite coffee order โ˜•. If you donโ€™t specify, you get your usual! They provide fallback values when arguments arenโ€™t supplied:

function makeCoffee(size: string = "medium", sugar: number = 1) {
  // If not specified, you get a medium coffee with 1 sugar!
}

๐ŸŽฏ Why Use Them?

Hereโ€™s why these features are game-changers:

  1. Flexible APIs ๐ŸŒˆ: Functions adapt to different use cases
  2. Backward Compatibility ๐Ÿ”„: Add parameters without breaking existing code
  3. Cleaner Code โœจ: No more checking for undefined values
  4. Better Developer Experience ๐Ÿ˜Š: Intuitive function interfaces
  5. Smart Defaults ๐Ÿง : Provide sensible fallbacks

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Optional Parameters

Letโ€™s start with optional parameters:

// ๐ŸŽจ Basic optional parameter
function greet(name: string, emoji?: string): string {
  if (emoji) {
    return `Hello ${name}! ${emoji}`;
  }
  return `Hello ${name}! ๐Ÿ‘‹`;
}

// โœจ Using the function
console.log(greet("Alice"));           // "Hello Alice! ๐Ÿ‘‹"
console.log(greet("Bob", "๐ŸŽ‰"));       // "Hello Bob! ๐ŸŽ‰"

// ๐Ÿ—๏ธ Multiple optional parameters
function createProfile(
  name: string,
  age?: number,
  city?: string,
  hobby?: string
): string {
  let profile = `Name: ${name}`;
  if (age) profile += `\nAge: ${age}`;
  if (city) profile += `\nCity: ${city}`;
  if (hobby) profile += `\nHobby: ${hobby}`;
  return profile;
}

// ๐ŸŽฏ Call with different combinations
console.log(createProfile("Sarah"));
console.log(createProfile("John", 25));
console.log(createProfile("Emma", 30, "NYC", "TypeScript ๐Ÿ’™"));

๐ŸŽจ Default Parameters

Now letโ€™s explore default parameters:

// โ˜• Function with default values
function orderCoffee(
  size: string = "medium",
  type: string = "latte",
  extras: string[] = []
): string {
  const order = `${size} ${type}`;
  if (extras.length > 0) {
    return `${order} with ${extras.join(", ")} โ˜•`;
  }
  return `${order} โ˜•`;
}

// ๐ŸŽฏ Various ways to call it
console.log(orderCoffee());                              // "medium latte โ˜•"
console.log(orderCoffee("large"));                       // "large latte โ˜•"
console.log(orderCoffee("small", "cappuccino"));         // "small cappuccino โ˜•"
console.log(orderCoffee("large", "mocha", ["whip", "caramel"])); // "large mocha with whip, caramel โ˜•"

// ๐Ÿš€ Default parameters with expressions
function createTask(
  title: string,
  priority: number = 1,
  dueDate: Date = new Date(Date.now() + 24 * 60 * 60 * 1000) // Tomorrow!
): object {
  return {
    title,
    priority,
    dueDate,
    created: new Date(),
    emoji: priority > 3 ? "๐Ÿ”ฅ" : "๐Ÿ“‹"
  };
}

๐Ÿ”„ Combining Optional and Default

// ๐ŸŽฎ Game character creation
function createCharacter(
  name: string,
  class: string = "warrior",
  level?: number,
  skills: string[] = ["basic attack"]
): object {
  return {
    name,
    class,
    level: level || 1, // If level not provided, default to 1
    skills,
    emoji: class === "mage" ? "๐Ÿง™" : class === "warrior" ? "โš”๏ธ" : "๐Ÿน"
  };
}

// ๐ŸŽฏ Flexible usage
const hero1 = createCharacter("Aragorn");
const hero2 = createCharacter("Gandalf", "mage", 50);
const hero3 = createCharacter("Legolas", "archer", undefined, ["arrow shot", "eagle eye"]);

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce API

Letโ€™s build a flexible product search function:

// ๐Ÿ›๏ธ Product interface
interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  emoji: string;
}

// ๐Ÿ” Flexible search function
function searchProducts(
  query: string,
  category?: string,
  maxPrice?: number,
  sortBy: "price" | "name" = "name",
  limit: number = 10
): Product[] {
  console.log(`๐Ÿ” Searching for: "${query}"`);
  console.log(`๐Ÿ“ Category: ${category || "all"}`);
  console.log(`๐Ÿ’ฐ Max price: ${maxPrice ? `$${maxPrice}` : "no limit"}`);
  console.log(`๐Ÿ“Š Sort by: ${sortBy}`);
  console.log(`๐Ÿ“ Limit: ${limit} results\n`);
  
  // Simulated search results
  const results: Product[] = [
    { id: "1", name: "TypeScript Handbook", price: 29.99, category: "books", inStock: true, emoji: "๐Ÿ“˜" },
    { id: "2", name: "Coffee Maker", price: 89.99, category: "appliances", inStock: true, emoji: "โ˜•" },
    { id: "3", name: "Mechanical Keyboard", price: 149.99, category: "electronics", inStock: false, emoji: "โŒจ๏ธ" }
  ];
  
  // Filter logic would go here
  return results.slice(0, limit);
}

// ๐ŸŽฏ Different ways to search
const search1 = searchProducts("typescript");
const search2 = searchProducts("coffee", "appliances");
const search3 = searchProducts("keyboard", undefined, 200, "price", 5);

// ๐Ÿ’ก Using undefined to skip parameters
const search4 = searchProducts("laptop", undefined, undefined, "price");

๐ŸŽจ Example 2: UI Component Builder

Letโ€™s create a flexible notification system:

// ๐Ÿ”” Notification types
type NotificationType = "success" | "error" | "warning" | "info";

interface NotificationOptions {
  title: string;
  message: string;
  type: NotificationType;
  duration: number;
  icon: string;
  position: string;
  sound: boolean;
}

// ๐ŸŽฏ Flexible notification function
function showNotification(
  title: string,
  message: string = "",
  type: NotificationType = "info",
  duration: number = 3000,
  options?: Partial<NotificationOptions>
): void {
  // Set up defaults with emojis
  const icons: Record<NotificationType, string> = {
    success: "โœ…",
    error: "โŒ",
    warning: "โš ๏ธ",
    info: "โ„น๏ธ"
  };
  
  const notification: NotificationOptions = {
    title,
    message,
    type,
    duration,
    icon: options?.icon || icons[type],
    position: options?.position || "top-right",
    sound: options?.sound ?? true
  };
  
  console.log(`\n${notification.icon} ${notification.type.toUpperCase()}`);
  console.log(`Title: ${notification.title}`);
  if (notification.message) console.log(`Message: ${notification.message}`);
  console.log(`Duration: ${notification.duration}ms`);
  console.log(`Position: ${notification.position}`);
  console.log(`Sound: ${notification.sound ? "๐Ÿ”Š" : "๐Ÿ”‡"}`);
}

// ๐Ÿš€ Usage examples
showNotification("Welcome!");
showNotification("Success!", "Your file has been saved", "success");
showNotification("Error", "Connection failed", "error", 5000);
showNotification(
  "Custom Alert",
  "This is fancy!",
  "warning",
  undefined,
  { icon: "๐Ÿšจ", position: "bottom-left", sound: false }
);

๐ŸŽฎ Example 3: Game Configuration

Letโ€™s build a game settings system:

// ๐ŸŽฎ Game configuration builder
interface GameConfig {
  playerName: string;
  difficulty: "easy" | "normal" | "hard" | "nightmare";
  startingLives: number;
  soundEnabled: boolean;
  musicVolume: number;
  effectsVolume: number;
  controls: "keyboard" | "gamepad" | "touch";
  emoji: string;
}

class GameSetup {
  // ๐Ÿ—๏ธ Create game with flexible options
  static createGame(
    playerName: string,
    difficulty: GameConfig["difficulty"] = "normal",
    customSettings?: Partial<GameConfig>
  ): GameConfig {
    // ๐ŸŽฏ Difficulty-based defaults
    const difficultyDefaults = {
      easy: { lives: 5, emoji: "๐Ÿ˜Š" },
      normal: { lives: 3, emoji: "๐ŸŽฎ" },
      hard: { lives: 2, emoji: "๐Ÿ’ช" },
      nightmare: { lives: 1, emoji: "๐Ÿ˜ˆ" }
    };
    
    const defaults = difficultyDefaults[difficulty];
    
    const config: GameConfig = {
      playerName,
      difficulty,
      startingLives: customSettings?.startingLives || defaults.lives,
      soundEnabled: customSettings?.soundEnabled ?? true,
      musicVolume: customSettings?.musicVolume ?? 70,
      effectsVolume: customSettings?.effectsVolume ?? 80,
      controls: customSettings?.controls || "keyboard",
      emoji: defaults.emoji
    };
    
    return config;
  }
  
  // ๐ŸŽจ Pretty print config
  static displayConfig(config: GameConfig): void {
    console.log(`\n${config.emoji} Game Configuration ${config.emoji}`);
    console.log(`๐Ÿ‘ค Player: ${config.playerName}`);
    console.log(`๐ŸŽฏ Difficulty: ${config.difficulty}`);
    console.log(`โค๏ธ Lives: ${config.startingLives}`);
    console.log(`๐Ÿ”Š Sound: ${config.soundEnabled ? "ON" : "OFF"}`);
    console.log(`๐ŸŽต Music: ${config.musicVolume}%`);
    console.log(`๐Ÿ’ฅ Effects: ${config.effectsVolume}%`);
    console.log(`๐ŸŽฎ Controls: ${config.controls}`);
  }
}

// ๐Ÿš€ Different game setups
const casualGame = GameSetup.createGame("Alice", "easy");
const normalGame = GameSetup.createGame("Bob");
const hardcoreGame = GameSetup.createGame(
  "Charlie",
  "nightmare",
  { soundEnabled: false, controls: "gamepad" }
);

GameSetup.displayConfig(casualGame);
GameSetup.displayConfig(hardcoreGame);

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Rest Parameters with Defaults

Combine rest parameters with optional/default parameters:

// ๐ŸŽฏ Advanced logging function
function log(
  level: "info" | "warn" | "error" = "info",
  prefix: string = "[LOG]",
  ...messages: unknown[]
): void {
  const emoji = {
    info: "โ„น๏ธ",
    warn: "โš ๏ธ",
    error: "โŒ"
  };
  
  const timestamp = new Date().toLocaleTimeString();
  console.log(`${emoji[level]} ${prefix} ${timestamp}:`, ...messages);
}

// ๐Ÿš€ Usage
log();                                    // โ„น๏ธ [LOG] 10:30:45:
log("info", "[APP]", "Server started");  // โ„น๏ธ [APP] 10:30:45: Server started
log("error", "[DB]", "Connection failed", { code: 500 }); // โŒ [DB] 10:30:45: Connection failed { code: 500 }

๐Ÿ—๏ธ Builder Pattern with Optional Parameters

Create powerful builders using optional parameters:

// ๐Ÿ  Advanced configuration builder
class ApiClientBuilder {
  private config: {
    baseUrl: string;
    timeout?: number;
    headers?: Record<string, string>;
    retries?: number;
    cache?: boolean;
    logger?: (message: string) => void;
  };
  
  constructor(baseUrl: string) {
    this.config = { baseUrl };
  }
  
  // ๐Ÿ”ง Chainable methods with defaults
  withTimeout(timeout: number = 5000): this {
    this.config.timeout = timeout;
    return this;
  }
  
  withHeaders(headers: Record<string, string> = {}): this {
    this.config.headers = { ...this.config.headers, ...headers };
    return this;
  }
  
  withRetries(retries: number = 3): this {
    this.config.retries = retries;
    return this;
  }
  
  withCache(enabled: boolean = true): this {
    this.config.cache = enabled;
    return this;
  }
  
  withLogger(logger?: (message: string) => void): this {
    this.config.logger = logger || console.log;
    return this;
  }
  
  // ๐Ÿš€ Build final client
  build(): object {
    return {
      ...this.config,
      timeout: this.config.timeout || 5000,
      retries: this.config.retries || 3,
      cache: this.config.cache ?? true,
      emoji: "๐ŸŒ"
    };
  }
}

// ๐ŸŽฏ Usage
const apiClient = new ApiClientBuilder("https://api.example.com")
  .withTimeout()
  .withRetries(5)
  .withCache()
  .build();

console.log("API Client:", apiClient);

๐ŸŽจ Generic Functions with Defaults

// ๐Ÿงฌ Generic function with default type parameter
function createCollection<T = string>(
  initialItems: T[] = [],
  maxSize: number = 100
): {
  items: T[];
  add: (item: T) => void;
  remove: (index: number) => void;
  emoji: string;
} {
  return {
    items: [...initialItems],
    add(item: T) {
      if (this.items.length < maxSize) {
        this.items.push(item);
        console.log(`โž• Added item (${this.items.length}/${maxSize})`);
      } else {
        console.log(`โŒ Collection full!`);
      }
    },
    remove(index: number) {
      if (index >= 0 && index < this.items.length) {
        this.items.splice(index, 1);
        console.log(`โž– Removed item at index ${index}`);
      }
    },
    emoji: "๐Ÿ“ฆ"
  };
}

// ๐ŸŽฎ Usage with different types
const stringCollection = createCollection(); // Default to string
const numberCollection = createCollection<number>([1, 2, 3], 5);
const emojiCollection = createCollection<string>(["๐ŸŽจ", "๐Ÿš€", "๐Ÿ’ก"]);

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Optional Parameters Order

// โŒ Wrong - optional parameters must come after required ones!
function badFunction(name?: string, age: number): void {
  // TypeScript error: Required parameter cannot follow optional parameter
}

// โœ… Correct - required first, then optional
function goodFunction(age: number, name?: string): void {
  console.log(`Age: ${age}, Name: ${name || "Anonymous"}`);
}

// ๐ŸŽฏ Or use default parameters (can be in any order)
function betterFunction(name: string = "Anonymous", age: number): void {
  console.log(`${name} is ${age} years old`);
}

๐Ÿคฏ Pitfall 2: undefined vs Default Values

// โš ๏ธ Tricky - passing undefined triggers default, but null doesn't!
function greet(name: string = "Friend"): string {
  return `Hello, ${name}!`;
}

console.log(greet());           // "Hello, Friend!" โœ…
console.log(greet(undefined));  // "Hello, Friend!" โœ…
console.log(greet(null as any)); // "Hello, null!" ๐Ÿ˜ฑ

// โœ… Better - handle null explicitly
function safeGreet(name: string | null = "Friend"): string {
  return `Hello, ${name || "Friend"}!`;
}

๐Ÿ˜ต Pitfall 3: Destructuring with Defaults

// โŒ Confusing - defaults in wrong place
function processUser({ name, age }: { name?: string; age?: number }) {
  // If name/age are undefined, we have no defaults here!
  console.log(`${name} is ${age} years old`); // Could be "undefined is undefined years old" ๐Ÿ˜ฑ
}

// โœ… Correct - defaults in destructuring
function processUser({
  name = "Anonymous",
  age = 0
}: {
  name?: string;
  age?: number;
} = {}): void {
  console.log(`${name} is ${age} years old`);
}

// ๐ŸŽฏ Even better - use interface
interface UserOptions {
  name?: string;
  age?: number;
  emoji?: string;
}

function createUser({
  name = "Anonymous",
  age = 18,
  emoji = "๐Ÿ‘ค"
}: UserOptions = {}): void {
  console.log(`${emoji} ${name} (${age})`);
}

// Usage
createUser();                          // ๐Ÿ‘ค Anonymous (18)
createUser({ name: "Alice" });         // ๐Ÿ‘ค Alice (18)
createUser({ name: "Bob", age: 25, emoji: "๐Ÿง‘โ€๐Ÿ’ป" }); // ๐Ÿง‘โ€๐Ÿ’ป Bob (25)

๐Ÿค” Pitfall 4: Type Inference with Defaults

// โš ๏ธ Be careful with type inference
function calculate(x = 0, y = 0) {
  // x and y are inferred as number
  return x + y;
}

// โŒ This will cause an error
// calculate("hello", "world"); // Error: string not assignable to number

// โœ… Be explicit when needed
function flexibleCalculate<T>(x: T = 0 as T, y: T = 0 as T): T {
  // More flexible but requires type assertions
  return (x as any) + (y as any);
}

๐Ÿ› ๏ธ Best Practices

1. ๐ŸŽฏ Order Parameters by Importance

// โœ… Good - most important/required first
function sendEmail(
  to: string,
  subject: string,
  body: string,
  cc?: string[],
  attachments?: File[],
  priority: "low" | "normal" | "high" = "normal"
) { /* ... */ }

2. ๐Ÿ“ Use Descriptive Defaults

// โœ… Good - defaults are self-documenting
function createButton(
  text: string,
  style: "primary" | "secondary" | "danger" = "primary",
  size: "small" | "medium" | "large" = "medium",
  disabled: boolean = false
) { /* ... */ }

3. ๐Ÿ›ก๏ธ Validate Optional Parameters

// โœ… Good - validate optional inputs
function processPayment(
  amount: number,
  currency: string = "USD",
  description?: string
): void {
  if (amount <= 0) {
    throw new Error("โŒ Amount must be positive");
  }
  
  if (description && description.length > 100) {
    console.warn("โš ๏ธ Description truncated to 100 characters");
    description = description.substring(0, 100);
  }
  
  console.log(`๐Ÿ’ณ Processing ${currency}${amount}`);
}

4. ๐ŸŽจ Use Object Parameters for Many Options

// โŒ Too many parameters
function createBadUser(
  name: string,
  email?: string,
  age?: number,
  country?: string,
  newsletter?: boolean,
  theme?: string
) { /* ... */ }

// โœ… Better - use options object
interface UserOptions {
  email?: string;
  age?: number;
  country?: string;
  newsletter?: boolean;
  theme?: string;
}

function createGoodUser(name: string, options: UserOptions = {}): void {
  const {
    email = "[email protected]",
    age = 0,
    country = "Unknown",
    newsletter = false,
    theme = "light"
  } = options;
  
  console.log(`๐Ÿ‘ค Creating user: ${name}`);
}

5. โœจ Document Default Behavior

/**
 * Formats a date string
 * @param date - The date to format
 * @param format - Format string (default: "YYYY-MM-DD")
 * @param locale - Locale for formatting (default: "en-US")
 * @returns Formatted date string
 */
function formatDate(
  date: Date,
  format: string = "YYYY-MM-DD",
  locale: string = "en-US"
): string {
  // Implementation
  return date.toLocaleDateString(locale);
}

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Flexible API Client

Create a type-safe HTTP client with flexible request options:

๐Ÿ“‹ Requirements:

  • โœ… Support GET, POST, PUT, DELETE methods
  • ๐Ÿ”ง Optional timeout and retry configuration
  • ๐ŸŽจ Custom headers with defaults
  • ๐Ÿ“Š Request/response logging options
  • ๐Ÿ›ก๏ธ Error handling with retries

๐Ÿš€ Bonus Points:

  • Add request caching
  • Implement request queuing
  • Create response transformers

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Flexible HTTP client with optional/default parameters
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

interface RequestOptions {
  headers?: Record<string, string>;
  timeout?: number;
  retries?: number;
  cache?: boolean;
  logLevel?: "none" | "basic" | "verbose";
}

interface ApiResponse<T> {
  data: T;
  status: number;
  cached: boolean;
  duration: number;
}

class ApiClient {
  constructor(
    private baseUrl: string,
    private defaultHeaders: Record<string, string> = {
      "Content-Type": "application/json"
    }
  ) {}
  
  // ๐Ÿš€ Main request method with flexible options
  async request<T>(
    endpoint: string,
    method: HttpMethod = "GET",
    body?: any,
    options: RequestOptions = {}
  ): Promise<ApiResponse<T>> {
    // ๐ŸŽจ Merge options with defaults
    const {
      headers = {},
      timeout = 5000,
      retries = 3,
      cache = method === "GET",
      logLevel = "basic"
    } = options;
    
    const url = `${this.baseUrl}${endpoint}`;
    const startTime = Date.now();
    
    // ๐Ÿ“ Logging
    if (logLevel !== "none") {
      console.log(`\n๐ŸŒ ${method} ${url}`);
      if (logLevel === "verbose") {
        console.log("๐Ÿ“‹ Headers:", { ...this.defaultHeaders, ...headers });
        if (body) console.log("๐Ÿ“ฆ Body:", body);
      }
    }
    
    // ๐Ÿ”„ Retry logic
    let lastError: Error | null = null;
    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        // Simulated fetch (replace with real implementation)
        const response = await this.simulatedFetch<T>(url, {
          method,
          headers: { ...this.defaultHeaders, ...headers },
          body: body ? JSON.stringify(body) : undefined,
          timeout
        });
        
        const duration = Date.now() - startTime;
        
        if (logLevel !== "none") {
          console.log(`โœ… Success (${duration}ms)`);
        }
        
        return {
          data: response,
          status: 200,
          cached: false,
          duration
        };
      } catch (error) {
        lastError = error as Error;
        if (attempt < retries) {
          console.log(`โš ๏ธ Attempt ${attempt + 1} failed, retrying...`);
          await this.delay(1000 * (attempt + 1)); // Exponential backoff
        }
      }
    }
    
    throw new Error(`โŒ Request failed after ${retries + 1} attempts: ${lastError?.message}`);
  }
  
  // ๐ŸŽฏ Convenience methods with smart defaults
  get<T>(endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "GET", undefined, options);
  }
  
  post<T>(
    endpoint: string,
    data?: any,
    options?: RequestOptions
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "POST", data, {
      cache: false,
      ...options
    });
  }
  
  put<T>(
    endpoint: string,
    data?: any,
    options?: RequestOptions
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "PUT", data, {
      cache: false,
      ...options
    });
  }
  
  delete<T>(
    endpoint: string,
    options?: RequestOptions
  ): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, "DELETE", undefined, {
      cache: false,
      ...options
    });
  }
  
  // ๐Ÿ› ๏ธ Helper methods
  private async simulatedFetch<T>(
    url: string,
    options: any
  ): Promise<T> {
    // Simulate network delay
    await this.delay(Math.random() * 1000);
    
    // Simulate successful response
    return { message: "Success!", timestamp: new Date() } as any;
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// ๐ŸŽฎ Usage examples
const api = new ApiClient("https://api.example.com");

// Simple GET request with defaults
api.get("/users");

// POST with custom options
api.post(
  "/users",
  { name: "Alice", email: "[email protected]" },
  { timeout: 10000, logLevel: "verbose" }
);

// GET with minimal logging and no retries
api.get("/health", { retries: 0, logLevel: "none" });

// PUT with custom headers
api.put(
  "/users/123",
  { name: "Alice Updated" },
  { headers: { "X-API-Key": "secret" } }
);

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered optional and default parameters! Hereโ€™s what you can now do:

  • โœ… Create flexible functions that adapt to different use cases ๐Ÿ’ช
  • โœ… Set smart defaults that make APIs intuitive ๐Ÿ›ก๏ธ
  • โœ… Combine optional and default parameters effectively ๐ŸŽฏ
  • โœ… Avoid common pitfalls with parameter ordering ๐Ÿ›
  • โœ… Build user-friendly APIs that developers love! ๐Ÿš€

Remember: Good defaults make happy developers! Your functions should be easy to use for simple cases but flexible enough for complex ones. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered optional and default parameters!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Complete the API client exercise above
  2. ๐Ÿ—๏ธ Refactor existing functions to use better defaults
  3. ๐Ÿ“š Move on to our next tutorial: Rest Parameters and Spread Operator
  4. ๐ŸŒŸ Create a library with a beautifully flexible API!

Remember: The best APIs feel natural to use. With optional and default parameters, youโ€™re building functions that are a joy to work with! ๐Ÿš€

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