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.99In this example, I’ve defined two function signatures for formatPrice, followed by the actual implementation that handles both cases.

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
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
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
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
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();
Best Practices for Function Overloading
- Keep overloads simple – Too many overloads can make your code harder to understand.
- Ensure implementation compatibility – The implementation signature must be compatible with all overload signatures.
- Order overloads from most specific to least specific – TypeScript checks overloads in order, so place more specific signatures first.
- Use generics when appropriate – For more flexible type handling that depends on input types.
- 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:
- TypeScript Program to Add Two Numbers
- Understanding TypeScript’s ‘satisfies’ Operator
- Understanding satisfies vs as Operator in TypeScript

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.