TypeScript
Function Overloading

Function Overloading

Function overloading allows to define multiple functions with the same name, but with different parameters. The function is called based on the parameters passed.

Defining

overloading.ts
function foo(arg1: string, arg2: string): string[];
function foo(arg1: number, arg2: number): number;
 
function foo(arg1: any, arg2: any): any {
  if (typeof arg1 === "string") return [arg1, arg2];
  return arg1 + arg2;
}
💡

It is worth noting that you do not have to check if typeof arg2 === string, because if arg1 is a string, then arg2 must also be a string and TS knows it.

Calling

overloading.ts
const one = foo("a", "b");
// returns ['a', 'b'], one: string[]
const two = foo(1, 2);
// returns 3, two: number
const three = foo("a", 10);
// error: No overload matches this call.

Alternative

An alternative to overloading is to use conditional types. In this case, TS also knows what type the returned value is, it also knows what type the parameters should be, but the construction of such a function itself is less elegant.

overloading.ts
type ConditionalType<T> = T extends string ? string[] : number;
 
function bar<T extends string | number>(arg1: T, arg2: T): ConditionalType<T> {
  if (typeof arg1 === "string") {
    return [arg1, arg2] as ConditionalType<T>;
  }
  if (typeof arg1 === "number") {
    return (arg1 + arg2) as ConditionalType<T>;
  }
}
 
const one = bar("a", "b");
// returns ['a', 'b'], one: string[]
const two = bar(1, 2);
// returns 3, two: number
const three = bar("a", 10);
// type error: Argument of type '10' is not assignable to parameter of type '"a"'

Example

Let's create a function that allows you to manipulate two types of arguments in such a way as to maintain the types returned by the function.

overloading.ts
import { useRouter } from "next/router";
 
const router = useRouter();
const query = router.query.myQuery;
// query: string | string[]
 
function manipulateQuery(query: string): string
function manipulateQuery(query: string[]): string[]
function manipulateQuery(query: string | string[]): string | string[]
 
function manipulateQuery(query: any): any {
  if (typeof query === "string") return query.toUpperCase();
  return query.map((querry) => querry.toUpperCase());
}
 
const manipulatedQuery = manipulateQuery(query);
// manipulatedQuery: string | string[]
const manipulatedQuery2 = manipulateQuery("foo");
// manipulatedQuery2: string
const manipulatedQuery3 = manipulateQuery(["foo", "bar"]);
// manipulatedQuery3: string[]
💡

In this case, we can observe that if the passed argument is any of the two possible types, TS expects the returned value to be any of the two possible types. However, if we use an argument of a specific type, TS will know that the returned value will have the same type.