TypeScript Function Overloading: Multiple Ways to Call the Same Function

While working on TypeScript, you are building a calculator app that can add numbers. Sometimes, you need to add two numbers, and sometimes you need to add three numbers.

You want to use the same function name, add, to handle both cases, so your code stays clean and easy to understand.

To do this, you use function overloading in TypeScript. It allows you to define multiple ways to call the add function with different numbers of parameters, while keeping one clear function name for your logic.

In this article, I’ll cover everything you need to know about TypeScript function overloading – from basic implementation to advanced use cases. So let’s dive in!

What is Function Overloading in TypeScript?

Function overloading allows a single function to have multiple signatures. This means you can define a function that can be called in different ways with different parameter types and return values.

Unlike some other languages, such as C# or Java, TypeScript’s implementation of function overloading is slightly different. You define multiple function signatures, but there’s only one implementation that handles all cases.

Basic Function Overloading in TypeScript

Method 1: Using Function Declaration Overloads in TypeScript

The most common way to implement function overloading in TypeScript is through declaration overloads. Here’s a simple example:

// Overload signatures
function formatPrice(price: number): string;
function formatPrice(price: number, currency: string): string;

// Implementation signature (must be compatible with all overloads)
function formatPrice(price: number, currency?: string): string {
  if (currency) {
    return `${currency}${price.toFixed(2)}`;
  }
  return `$${price.toFixed(2)}`;
}

// Usage
console.log(formatPrice(49.99));        // Outputs: $49.99
console.log(formatPrice(49.99, "€"));   // Outputs: €49.99

In this example, I’ve defined two function signatures for formatPrice, followed by the actual implementation that handles both cases.

TypeScript Function Overloading

Method 2: Using Interface Method Overloads in TypeScript

When working with classes and interfaces, you can define method overloads:

interface ShoppingCart {
  addItem(id: number, quantity: number): void;
  addItem(item: { id: number, quantity: number }): void;
}

class Cart implements ShoppingCart {
  items: { id: number, quantity: number }[] = [];

  // Implementation that handles both overloaded methods with debug logs
  addItem(idOrItem: number | { id: number, quantity: number }, quantity?: number): void {
    if (typeof idOrItem === 'object') {
      console.log(`Adding item using object: id=${idOrItem.id}, quantity=${idOrItem.quantity}`);
      this.items.push(idOrItem);
    } else {
      console.log(`Adding item using id and quantity: id=${idOrItem}, quantity=${quantity}`);
      this.items.push({ id: idOrItem, quantity: quantity! });
    }
    console.log(`Current cart items:`, this.items);
  }
}

// Usage
const cart = new Cart();
cart.addItem(101, 2);                    // First overload
cart.addItem({ id: 202, quantity: 1 });  // Second overload
Use Interface Method Overloading in TypeScript

Method 3: Using Function Types with Overloads in TypeScript

You can also define function types with overloads:

// Define a type with overloaded call signatures
type SearchFunction = {
  (term: string): string[];
  (term: string, maxResults: number): string[];
}

// Implement the function
const search: SearchFunction = (term: string, maxResults?: number): string[] => {
  const allResults = [`Result for ${term}`];

  if (maxResults !== undefined) {
    return allResults.slice(0, maxResults);
  }

  return allResults;
};

// Usage
search("TypeScript");       // Returns all results
search("TypeScript", 10);   // Returns up to 10 results
Using Function Types with Overloads in TypeScript

Advanced Function Overloading Techniques

Method 4: Overloading with Different Return Types in TypeScript

One of the most powerful aspects of function overloading is returning different types based on the inputs:

// Different return types based on input
function getUserData(id: number): { id: number, name: string };
function getUserData(email: string): { email: string, name: string };
function getUserData(idOrEmail: number | string): any {
  if (typeof idOrEmail === 'number') {
    // Fetch user by ID
    return { id: idOrEmail, name: "Jane Doe" };
  } else {
    // Fetch user by email
    return { email: idOrEmail, name: "John Smith" };
  }
}

// TypeScript correctly infers return types
const userById = getUserData(123);       // Type: { id: number, name: string }
const userByEmail = getUserData("user@example.com");  // Type: { email: string, name: string }

console.log(userById.id);          // OK
console.log(userByEmail.email);    // OK
Overloading with Different Return Types in TypeScript

Method 5: Generic Function Overloading in TypeScript

Combining generics with function overloading provides even more flexibility:

// Generic function overloading
function convert<T extends number>(value: T): string;
function convert<T extends string>(value: T): number;
function convert<T extends string | number>(value: T): string | number {
  if (typeof value === 'string') {
    return parseFloat(value) || 0;
  } else {
    return value.toString();
  }
}

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

console.log(typeof stringResult);  // string
console.log(typeof numberResult);  // number
Generic Function Overloading in TypeScript

Real-World Example: Building a Flexible API Client With Method Overloading in TypeScript

Let’s look at a practical example of how function overloading might be used in a real-world scenario – an API client for a U.S. weather service:

interface WeatherResponse {
  temperature: number;
  conditions: string;
  humidity: number;
}

// Function overloads
function getWeather(): Promise<WeatherResponse>;
function getWeather(zipCode: string): Promise<WeatherResponse>;
function getWeather(lat: number, long: number): Promise<WeatherResponse>;
async function getWeather(
  zipCodeOrLat?: string | number,
  long?: number
): Promise<WeatherResponse> {
  let url = "";

  // Default to New York City if no parameters
  if (zipCodeOrLat === undefined) {
    console.log("Fetching weather for New York City...");
    url = `https://wttr.in/New+York?format=j1`;
  }
  // Handle zip code (using city name as fallback)
  else if (typeof zipCodeOrLat === 'string') {
    console.log(`Fetching weather for ZIP/City: ${zipCodeOrLat}...`);
    url = `https://wttr.in/${zipCodeOrLat}?format=j1`;
  }
  // Handle latitude/longitude
  else if (typeof zipCodeOrLat === 'number' && typeof long === 'number') {
    console.log(`Fetching weather for coordinates: ${zipCodeOrLat}, ${long}...`);
    url = `https://wttr.in/${zipCodeOrLat},${long}?format=j1`;
  } else {
    throw new Error('Invalid parameters');
  }

  const response = await fetch(url);
  const data = await response.json();

  // Extract and normalize weather data for consistency
  const currentCondition = data.current_condition[0];
  const weather: WeatherResponse = {
    temperature: parseInt(currentCondition.temp_F),
    conditions: currentCondition.weatherDesc[0].value,
    humidity: parseInt(currentCondition.humidity)
  };

  console.log("Weather data fetched:", weather);
  return weather;
}

// Usage example
async function checkWeather() {
  try {
    const nyWeather = await getWeather(); // Default
    const bostonWeather = await getWeather("Boston"); // By city
    const chicagoWeather = await getWeather(41.8781, -87.6298); // By coordinates

    console.log(`New York: ${nyWeather.temperature}°F, ${nyWeather.conditions}`);
    console.log(`Boston: ${bostonWeather.temperature}°F, ${bostonWeather.conditions}`);
    console.log(`Chicago: ${chicagoWeather.temperature}°F, ${chicagoWeather.conditions}`);
  } catch (error) {
    console.error("Error fetching weather data:", error);
  }
}

checkWeather();
API Client With Method Overloading in TypeScript

Best Practices for Function Overloading

  1. Keep overloads simple – Too many overloads can make your code harder to understand.
  2. Ensure implementation compatibility – The implementation signature must be compatible with all overload signatures.
  3. Order overloads from most specific to least specific – TypeScript checks overloads in order, so place more specific signatures first.
  4. Use generics when appropriate – For more flexible type handling that depends on input types.
  5. Document each overload – When using JSDoc, document each overload separately.

Common Pitfalls to Avoid

Pitfall 1: Incompatible Implementation

// ❌ WRONG
function process(x: number): number;
function process(x: string): string;
function process(x: boolean): boolean { // Error: Implementation not compatible with overloads
  return x;
}

Pitfall 2: Forgetting Optional Parameters

// ❌ WRONG
function buildUrl(path: string): string;
function buildUrl(path: string, params: Record<string, string>): string;
function buildUrl(path: string, params: Record<string, string>) { // Error: missing return type
  // Implementation
}

// ✅ CORRECT
function buildUrl(path: string): string;
function buildUrl(path: string, params: Record<string, string>): string;
function buildUrl(path: string, params?: Record<string, string>): string {
  // Implementation
  return '';
}

When to Use (and Not Use) Function Overloading

Function overloading is significant when:

  • You need a function to handle different parameter types
  • The function’s behavior changes based on input types
  • You want to provide better type checking and IntelliSense

You might want to avoid overloading when:

  • Simple union types would work just as well
  • The implementation becomes overly complex
  • The different behaviors aren’t closely related

Conclusion

I hope you found this article helpful! I have explained TypeScript function overloading – from basic implementation to advanced use cases.

TypeScript function overloading is a powerful tool that can make your code more type-safe and easier to use.

You may like to read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.