JavaScript and TypeScript course Β· Module 6: TypeScript Basics

Function Overloading

13 min read
In this lesson9

"In Jurassic Park, precision is key," says Dr. Henry Wu, looking at his highly specialized laboratory equipment. "The same DNA analyzer can examine samples in different formats - from dinosaur blood, through fossils, to amber. Similarly in TypeScript, one function can handle different sets of parameters and return different types of data."

Welcome to the world of function overloading - a powerful TypeScript mechanism that allows defining functions that accept different kinds of parameters and return different result types.

What is function overloading?

Function overloading is a mechanism that enables defining multiple signatures for a single function. This way we can create a function that behaves differently depending on the number, types, and structure of the passed parameters.

In other words, just as scientists in Jurassic Park can use the same equipment for different procedures, we can use one function for different operations, while maintaining full type safety.

Basics of function overloading

In TypeScript, function overloading consists of two parts:

  1. Overload signatures - definitions of parameter types and return values
  2. Implementation signature - the actual function implementation, which must be compatible with all overload signatures

Let's look at a simple example:

1// Overload signatures
2function analyzeSample(id: number): string;
3function analyzeSample(nazwa: string): string;
4
5// Implementation signature
6function analyzeSample(argument: number | string): string {
7  if (typeof argument === "number") {
8    return `Analyzing sample with ID: ${argument}`;
9  } else {
10    return `Analyzing sample: ${argument}`;
11  }
12}
13
14// Using the function
15const wynik1 = analyzeSample(42);         // TypeScript knows the argument is number
16const wynik2 = analyzeSample("Raptor-01"); // TypeScript knows the argument is string
17// const error = analyzeSample(true);      // Error, boolean is not an accepted type

In the above example:

  1. We defined two overload signatures - one accepting number, another accepting string
  2. We created one implementation that handles both types
  3. TypeScript ensures the function is only called with arguments of the correct types

Overloading with different return types

The true power of overloading is revealed when a function can return different types depending on the input parameters:

1// Overload signatures with different return types
2function pobierzDaneDinosaura(id: number): { id: number; species: string; wiek: number };
3function pobierzDaneDinosaura(nazwa: string): { nazwa: string; species: string };
4
5// Implementation signature
6function pobierzDaneDinosaura(identyfikator: number | string): any {
7  if (typeof identyfikator === "number") {
8    // Fetching full data based on ID
9    return {
10      id: identyfikator,
11      species: "Tyrannosaurus",
12      wiek: 7
13    };
14  } else {
15    // Fetching basic information based on name
16    return {
17      nazwa: identyfikator,
18      species: "Velociraptor"
19    };
20  }
21}
22
23// Usage with full type information
24const dinosaurPoId = pobierzDaneDinosaura(42);
25console.log(dinosaurPoId.wiek);  // OK, TypeScript knows that property 'wiek' exists
26
27const dinosaurPoNazwie = pobierzDaneDinosaura("Blue");
28// console.log(dinosaurPoNazwie.wiek);  // Error, TypeScript knows the object has no 'wiek' property
29console.log(dinosaurPoNazwie.species);  // OK

Note that despite using any in the implementation signature, TypeScript provides full type safety when using the function, based on the overload signatures.

Overloading with different numbers of parameters

We can also overload functions that accept different numbers of parameters:

1// Overloading with different number of parameters
2function monitorujDinosaura(id: number): string;
3function monitorujDinosaura(id: number, detailLevel: "podstawowy" | "full"): Object;
4function monitorujDinosaura(id: number, detailLevel?: "podstawowy" | "full"): string | Object {
5  if (!detailLevel) {
6    return `Monitoring dinosaur with ID ${id}...`;
7  } else if (detailLevel === "podstawowy") {
8    return {
9      status: "active",
10      lokalizacja: "Sector B"
11    };
12  } else {
13    return {
14      status: "active",
15      lokalizacja: "Sector B",
16      speed: "2 km/h",
17      heartRate: 80,
18      bodyTemperature: 38,
19      lastMeal: "2 hours ago",
20      poziomAgresji: "low"
21    };
22  }
23}
24
25// Usage
26const komunikat = monitorujDinosaura(5);  // Returns string
27const podstawoweDane = monitorujDinosaura(5, "podstawowy");  // Returns simple object
28const detailedData = monitorujDinosaura(5, "full");  // Returns extended object

Method overloading in classes

Function overloading can also be applied to methods in classes:

1class EkipaBadawcza {
2  // Overload signatures for wyslijZespol method
3  wyslijZespol(cel: { x: number, y: number }): string;
4  wyslijZespol(sektor: string): string;
5  wyslijZespol(cel: string, wielkosc: number): string;
6
7  // Method implementation
8  wyslijZespol(cel: { x: number, y: number } | string, wielkosc?: number): string {
9    if (typeof cel === "object") {
10      return `Sending team to coordinates: ${cel.x}, ${cel.y}`;
11    } else if (wielkosc !== undefined) {
12      return `Sending a ${wielkosc}-person team to sector: ${cel}`;
13    } else {
14      return `Sending standard team to sector: ${cel}`;
15    }
16  }
17}
18
19const ekipa = new EkipaBadawcza();
20console.log(ekipa.wyslijZespol({ x: 123, y: 456 }));  // Using coordinates
21console.log(ekipa.wyslijZespol("B-5"));  // Using sector name
22console.log(ekipa.wyslijZespol("C-7", 5));  // Using sector name and team size

Advanced function overloading examples

Different types for different parameter combinations

1// Overload signatures for complex analysis
2function analizujZachowanie(
3  speciesId: number,
4  daneBehawioralne: number[]
5): { poziomAgresji: number; ryzyko: string };
6
7function analizujZachowanie(
8  speciesId: number,
9  daneBehawioralne: number[],
10  historyczneDane: boolean
11): { poziomAgresji: number; ryzyko: string; comparison: string };
12
13// Implementation
14function analizujZachowanie(
15  speciesId: number,
16  daneBehawioralne: number[],
17  historyczneDane?: boolean
18): any {
19  // Basic analysis
20  const averageActivity = daneBehawioralne.reduce((a, b) => a + b, 0) / daneBehawioralne.length;
21  const poziomAgresji = Math.round(averageActivity * 10) / 10;
22
23  let ryzyko = "low";
24  if (poziomAgresji > 7) ryzyko = "high";
25  else if (poziomAgresji > 4) ryzyko = "medium";
26
27  // Basic result
28  const wynik = { poziomAgresji, ryzyko };
29
30  // Extended result if historical data requested
31  if (historyczneDane) {
32    // Simulating retrieval of historical data
33    const historycznyPoziom = 3.5;
34    const zmiana = poziomAgresji - historycznyPoziom;
35    const comparison = zmiana > 0
36      ? `Increase by ${zmiana.toFixed(1)} pts`
37      : `Decrease by ${Math.abs(zmiana).toFixed(1)} pts`;
38
39    return { ...wynik, comparison };
40  }
41
42  return wynik;
43}
44
45// Usage
46const podstawowaAnaliza = analizujZachowanie(1, [2, 4, 3, 6, 1]);
47console.log(podstawowaAnaliza.ryzyko);  // "medium"
48
49const rozszerzonaAnaliza = analizujZachowanie(1, [2, 4, 3, 6, 1], true);
50console.log(rozszerzonaAnaliza.comparison);  // "Increase by 0.8 pts"

Generic function overloads

Function overloads can be even more powerful when combined with generic types:

1// Generic function overloads
2function pobierzDane<T extends { id: number }>(id: number): T;
3function pobierzDane<T extends { nazwa: string }>(nazwa: string): T;
4function pobierzDane<T>(identyfikator: number | string): T {
5  // Implementation communicating with the database and returning the appropriate type
6  if (typeof identyfikator === "number") {
7    // Simulating different data types for different IDs
8    if (identyfikator < 100) {
9      // Dinosaur
10      return {
11        id: identyfikator,
12        species: "Tyrannosaurus",
13        wiek: 7,
14        waga: 7500
15      } as unknown as T;
16    } else {
17      // Employee
18      return {
19        id: identyfikator,
20        imie: "Owen",
21        nazwisko: "Grady",
22        stanowisko: "Trainer"
23      } as unknown as T;
24    }
25  } else {
26    // Search by name
27    if (identyfikator.includes("REX")) {
28      return {
29        nazwa: identyfikator,
30        species: "Tyrannosaurus",
31        populacja: 1
32      } as unknown as T;
33    } else {
34      return {
35        nazwa: identyfikator,
36        typ: "Sector",
37        capacity: 12
38      } as unknown as T;
39    }
40  }
41}
42
43// Interfaces for typing
44interface Dinosaur {
45  id: number;
46  species: string;
47  wiek: number;
48  waga: number;
49}
50
51interface Pracownik {
52  id: number;
53  imie: string;
54  nazwisko: string;
55  stanowisko: string;
56}
57
58interface Gatunek {
59  nazwa: string;
60  species: string;
61  populacja: number;
62}
63
64// Usage with explicit generic type
65const trex = pobierzDane<Dinosaur>(1);
66console.log(trex.wiek);  // OK, TypeScript knows this is a Dinosaur
67
68const pracownik = pobierzDane<Pracownik>(101);
69console.log(pracownik.stanowisko);  // OK, TypeScript knows this is a Pracownik
70
71const species = pobierzDane<Gatunek>("T-REX-01");
72console.log(species.populacja);  // OK, TypeScript knows this is a Gatunek

Best practices and pitfalls

1. The implementation signature must be compatible with all overload signatures

Parameters in the implementation signature must be defined so they can handle all possible combinations from the overload signatures:

1// GOOD:
2function testuj(a: string): number;
3function testuj(a: number, b: boolean): string;
4function testuj(a: string | number, b?: boolean): number | string {
5  // Implementation
6  return 0 as any;
7}
8
9// BAD:
10function zle(a: string): number;
11function zle(a: number, b: boolean): string;
12// Error: parameter 'b' in the implementation must be optional
13function zle(a: string | number, b: boolean): number | string {
14  return 0 as any;
15}

2. Overload signatures are checked in order

TypeScript checks overload signatures from top to bottom, so more specific signatures should be defined before more general ones:

1// BAD - more general signature shadows more specific one
2function wrongOrder(dane: any[]): number;
3function wrongOrder(dane: number[]): string;  // This signature will never be matched
4function wrongOrder(dane: any[]): number | string {
5  if (Array.isArray(dane) && dane.every(d => typeof d === "number")) {
6    return "Array of numbers";
7  } else {
8    return dane.length;
9  }
10}
11
12// GOOD - more specific signature comes first
13function correctOrder(dane: number[]): string;
14function correctOrder(dane: any[]): number;
15function correctOrder(dane: any[]): number | string {
16  if (Array.isArray(dane) && dane.every(d => typeof d === "number")) {
17    return "Array of numbers";
18  } else {
19    return dane.length;
20  }
21}

3. Balancing between overloading, optional parameters, and union types

Sometimes optional parameters or union types may be a better solution than extensive overloads:

1// Instead of many overloads:
2function opcja1(id: number): void;
3function opcja1(id: number, tryb: string): void;
4function opcja1(id: number, tryb: string, extra: boolean): void;
5// ...and many more versions
6
7// Better to use optional parameters:
8function opcja2(id: number, tryb?: string, extra?: boolean): void {
9  // Implementation
10}

Practical example: Jurassic Park management system

Let's look at a comprehensive example of how function overloading can be used in a Jurassic Park management system:

1// Defining types used in the system
2type StatusDinosaura = "zdrowy" | "chory" | "w leczeniu" | "w coma";
3type GatunekDinosaura = "Tyrannosaurus" | "Velociraptor" | "Triceratops" | "Brachiosaurus";
4type ThreatLevelType = 1 | 2 | 3 | 4 | 5;
5
6interface DanePodstawowe {
7  id: number;
8  nazwa: string;
9  species: GatunekDinosaura;
10  status: StatusDinosaura;
11  threatLevel: ThreatLevelType;
12}
13
14interface DaneZdrowotne {
15  bodyTemperature: number;
16  heartRate: number;
17  czynnikStresowy: number;
18  lastMeal: Date;
19  wagaKg: number;
20}
21
22interface DaneLokalizacyjne {
23  sektor: string;
24  gpsCoordinates: [number, number];
25  ostatniaAktualizacja: Date;
26}
27
28interface SecurityReport {
29  statusOgrodzenia: "sprawne" | "uszkodzone" | "w naprawie";
30  zasilanieAktywne: boolean;
31  osobyWSektorze: number;
32  alertyAktywne: string[];
33}
34
35// Class for managing dinosaurs with overloaded methods
36class DinosaurManagementSystem {
37  private bazaDanych: Map<number, {
38    podstawowe: DanePodstawowe;
39    zdrowotne?: DaneZdrowotne;
40    lokalizacyjne?: DaneLokalizacyjne;
41    security?: SecurityReport;
42  }> = new Map();
43
44  // Method for adding a dinosaur to the system
45  dodajDinosaura(dane: DanePodstawowe): number;
46  dodajDinosaura(dane: DanePodstawowe, zdrowotne: DaneZdrowotne): number;
47  dodajDinosaura(dane: DanePodstawowe, zdrowotne: DaneZdrowotne, lokalizacja: DaneLokalizacyjne): number;
48  dodajDinosaura(
49    dane: DanePodstawowe,
50    zdrowotne?: DaneZdrowotne,
51    lokalizacja?: DaneLokalizacyjne
52  ): number {
53    const nowyRekord = {
54      podstawowe: dane,
55      zdrowotne,
56      lokalizacyjne: lokalizacja
57    };
58
59    this.bazaDanych.set(dane.id, nowyRekord);
60    console.log(`Added dinosaur: ${dane.nazwa} (ID: ${dane.id})`);
61    return dane.id;
62  }
63
64  // Overloaded method for retrieving dinosaur data
65  pobierzDane(id: number): DanePodstawowe;
66  pobierzDane(id: number, typ: "zdrowie"): DaneZdrowotne | undefined;
67  pobierzDane(id: number, typ: "lokalizacja"): DaneLokalizacyjne | undefined;
68  pobierzDane(id: number, typ: "security"): SecurityReport | undefined;
69  pobierzDane(id: number, typ: "wszystko"): {
70    podstawowe: DanePodstawowe;
71    zdrowotne?: DaneZdrowotne;
72    lokalizacyjne?: DaneLokalizacyjne;
73    security?: SecurityReport;
74  };
75  pobierzDane(id: number, typ?: "zdrowie" | "lokalizacja" | "security" | "wszystko"): any {
76    const rekord = this.bazaDanych.get(id);
77
78    if (!rekord) {
79      throw new Error(`Dinosaur with ID ${id} does not exist in the system.`);
80    }
81
82    if (!typ) {
83      return rekord.podstawowe;
84    }
85
86    switch (typ) {
87      case "zdrowie":
88        return rekord.zdrowotne;
89      case "lokalizacja":
90        return rekord.lokalizacyjne;
91      case "security":
92        return rekord.security;
93      case "wszystko":
94        return rekord;
95    }
96  }
97
98  // Overloaded method for updating data
99  aktualizujDane(id: number, dane: Partial<DanePodstawowe>): boolean;
100  aktualizujDane(id: number, dane: DaneZdrowotne, typ: "zdrowie"): boolean;
101  aktualizujDane(id: number, dane: DaneLokalizacyjne, typ: "lokalizacja"): boolean;
102  aktualizujDane(id: number, dane: SecurityReport, typ: "security"): boolean;
103  aktualizujDane(
104    id: number,
105    dane: Partial<DanePodstawowe> | DaneZdrowotne | DaneLokalizacyjne | SecurityReport,
106    typ?: "zdrowie" | "lokalizacja" | "security"
107  ): boolean {
108    const rekord = this.bazaDanych.get(id);
109
110    if (!rekord) {
111      console.error(`Dinosaur with ID ${id} does not exist in the system.`);
112      return false;
113    }
114
115    if (!typ) {
116      // Updating basic data
117      rekord.podstawowe = { ...rekord.podstawowe, ...dane as Partial<DanePodstawowe> };
118      console.log(`Updated basic data for dinosaur ID: ${id}`);
119    } else {
120      switch (typ) {
121        case "zdrowie":
122          rekord.zdrowotne = { ...(rekord.zdrowotne || {}), ...dane as DaneZdrowotne };
123          console.log(`Updated health data for dinosaur ID: ${id}`);
124          break;
125        case "lokalizacja":
126          rekord.lokalizacyjne = { ...(rekord.lokalizacyjne || {}), ...dane as DaneLokalizacyjne };
127          console.log(`Updated location data for dinosaur ID: ${id}`);
128          break;
129        case "security":
130          rekord.security = { ...(rekord.security || {}), ...dane as SecurityReport };
131          console.log(`Updated security data for dinosaur ID: ${id}`);
132          break;
133      }
134    }
135
136    this.bazaDanych.set(id, rekord);
137    return true;
138  }
139
140  // Overloaded method for generating reports
141  generujRaport(): string; // Report for all dinosaurs
142  generujRaport(species: GatunekDinosaura): string; // Report for selected species
143  generujRaport(threatLevel: ThreatLevelType): string; // Report for threat level
144  generujRaport(filter?: GatunekDinosaura | ThreatLevelType): string {
145    let dinosaury = Array.from(this.bazaDanych.values()).map(d => d.podstawowe);
146
147    if (filter) {
148      if (typeof filter === "string") {
149        // Filtering by species
150        dinosaury = dinosaury.filter(d => d.species === filter);
151        return this.formatujRaport(`Report for species: ${filter}`, dinosaury);
152      } else {
153        // Filtering by threat level
154        dinosaury = dinosaury.filter(d => d.threatLevel === filter);
155        return this.formatujRaport(`Report for threat level: ${filter}`, dinosaury);
156      }
157    }
158
159    return this.formatujRaport("Full report of all dinosaurs", dinosaury);
160  }
161
162  private formatujRaport(title: string, dinosaury: DanePodstawowe[]): string {
163    let raport = `=== ${title} ===\n`;
164    raport += `Date: ${new Date().toLocaleString()}\n`;
165    raport += `Number of dinosaurs: ${dinosaury.length}\n\n`;
166
167    dinosaury.forEach(d => {
168      raport += `ID: ${d.id} | ${d.nazwa} | ${d.species} | Status: ${d.status} | Threat: ${d.threatLevel}\n`;
169    });
170
171    return raport;
172  }
173}
174
175// Example usage of the system
176const system = new DinosaurManagementSystem();
177
178// Adding dinosaurs with different data sets
179const rexId = system.dodajDinosaura({
180  id: 1,
181  nazwa: "Rexy",
182  species: "Tyrannosaurus",
183  status: "zdrowy",
184  threatLevel: 5
185});
186
187const raptorId = system.dodajDinosaura(
188  {
189    id: 2,
190    nazwa: "Blue",
191    species: "Velociraptor",
192    status: "zdrowy",
193    threatLevel: 4
194  },
195  {
196    bodyTemperature: 38.5,
197    heartRate: 85,
198    czynnikStresowy: 2.1,
199    lastMeal: new Date(Date.now() - 3600000), // 1 hour ago
200    wagaKg: 160
201  }
202);
203
204const triceratopsId = system.dodajDinosaura(
205  {
206    id: 3,
207    nazwa: "Tricy",
208    species: "Triceratops",
209    status: "zdrowy",
210    threatLevel: 2
211  },
212  {
213    bodyTemperature: 37.2,
214    heartRate: 45,
215    czynnikStresowy: 1.3,
216    lastMeal: new Date(Date.now() - 7200000), // 2 hours ago
217    wagaKg: 8500
218  },
219  {
220    sektor: "B-7",
221    gpsCoordinates: [34.5678, -118.2345],
222    ostatniaAktualizacja: new Date()
223  }
224);
225
226// Retrieving different types of data
227const rexPodstawowe = system.pobierzDane(rexId);
228console.log(`Dinosaur ${rexPodstawowe.nazwa} has threat level ${rexPodstawowe.threatLevel}`);
229
230const raptorZdrowie = system.pobierzDane(raptorId, "zdrowie");
231if (raptorZdrowie) {
232  console.log(`Body temperature of raptor: ${raptorZdrowie.bodyTemperature}Β°C`);
233}
234
235const triceratopsLokalizacja = system.pobierzDane(triceratopsId, "lokalizacja");
236if (triceratopsLokalizacja) {
237  console.log(`Triceratops is located in sector: ${triceratopsLokalizacja.sektor}`);
238}
239
240// Updating data
241system.aktualizujDane(rexId, { status: "w leczeniu" });
242
243system.aktualizujDane(raptorId,
244  {
245    bodyTemperature: 39.2,
246    heartRate: 95,
247    czynnikStresowy: 4.5,
248    lastMeal: new Date(),
249    wagaKg: 162
250  },
251  "zdrowie"
252);
253
254// Generating reports
255console.log(system.generujRaport());
256console.log(system.generujRaport("Velociraptor"));
257console.log(system.generujRaport(5));

Summary

Function overloading is a powerful TypeScript mechanism that allows creating flexible APIs while maintaining full type safety. Just as scientists in Jurassic Park must adapt their procedures to different dinosaur species, programmers can create functions that adapt to different sets of parameters.

The most important rules of function overloading:

  1. Define overload signatures before the implementation signature
  2. The implementation signature must be compatible with all overload signatures
  3. Place more specific signatures before more general ones
  4. Use overloads when you want different return types for different input parameters
  5. Consider alternatives (optional parameters, union types) for simpler cases

"In programming, just as in dinosaur genetics," says Dr. Wu with a smile, "the true power lies in the ability to adapt. Function overloading is like creating adaptive DNA sequences - the same function can evolve to serve different purposes, while maintaining its integrity and safety."

Code for this lesson: index.ts
1// Function overloading in Jurassic Park
2console.log("TypeScript function overloading\n");
3
4// ===========================================
5// 1. Basic overloading
6// ===========================================
7console.log("=== BASIC OVERLOADING ===");
8
9// Overload definitions
10function identify(value: string): string;
11function identify(value: number): number;
12function identify(value: boolean): boolean;
13
14// Implementation
15function identify(value: string | number | boolean): string | number | boolean {
16  return value;
17}
18
19console.log("String:", identify("T-Rex"));
20console.log("Number:", identify(8000));
21console.log("Boolean:", identify(true));
22
23// ===========================================
24// 2. Overloading with different parameters
25// ===========================================
26console.log("\n=== DIFFERENT PARAMETERS ===");
27
28// Overloads
29function createDinosaur(name: string): string;
30function createDinosaur(name: string, species: string): string;
31function createDinosaur(name: string, species: string, age: number): string;
32
33// Implementation
34function createDinosaur(
35  name: string,
36  species?: string,
37  age?: number
38): string {
39  let result = `Dinosaur: ${name}`;
40  if (species) result += `, Species: ${species}`;
41  if (age !== undefined) result += `, Age: ${age}`;
42  return result;
43}
44
45console.log(createDinosaur("Rexy"));
46console.log(createDinosaur("Blue", "Velociraptor"));
47console.log(createDinosaur("Charlie", "Velociraptor", 3));
48
49// ===========================================
50// 3. Overloading with different returns
51// ===========================================
52console.log("\n=== DIFFERENT RETURN TYPES ===");
53
54interface Dinosaur {
55  name: string;
56  species: string;
57  age: number;
58}
59
60// Overloads with different return types
61function findDinosaur(id: number): Dinosaur | undefined;
62function findDinosaur(name: string): Dinosaur[];
63
64// Implementation
65function findDinosaur(
66  query: number | string
67): Dinosaur | undefined | Dinosaur[] {
68  const database: Dinosaur[] = [
69    { name: "Rexy", species: "T-Rex", age: 5 },
70    { name: "Blue", species: "Velociraptor", age: 3 },
71    { name: "Charlie", species: "Velociraptor", age: 3 }
72  ];
73
74  if (typeof query === "number") {
75    return database[query];
76  } else {
77    return database.filter(d => d.name.includes(query));
78  }
79}
80
81console.log("Find by ID:", findDinosaur(0));
82console.log("Find by name:", findDinosaur("Blue"));
83
84// ===========================================
85// 4. Overloading with objects
86// ===========================================
87console.log("\n=== OVERLOADING WITH OBJECTS ===");
88
89interface SearchByName {
90  type: "name";
91  name: string;
92}
93
94interface SearchBySpecies {
95  type: "species";
96  species: string;
97}
98
99interface SearchByAge {
100  type: "age";
101  minAge: number;
102  maxAge: number;
103}
104
105type SearchCriteria = SearchByName | SearchBySpecies | SearchByAge;
106
107// Overloads
108function search(criteria: SearchByName): Dinosaur[];
109function search(criteria: SearchBySpecies): Dinosaur[];
110function search(criteria: SearchByAge): Dinosaur[];
111
112// Implementation
113function search(criteria: SearchCriteria): Dinosaur[] {
114  const database: Dinosaur[] = [
115    { name: "Rexy", species: "T-Rex", age: 5 },
116    { name: "Blue", species: "Velociraptor", age: 3 },
117    { name: "Charlie", species: "Velociraptor", age: 3 },
118    { name: "Delta", species: "Velociraptor", age: 4 }
119  ];
120
121  if (criteria.type === "name") {
122    return database.filter(d => d.name === criteria.name);
123  } else if (criteria.type === "species") {
124    return database.filter(d => d.species === criteria.species);
125  } else {
126    return database.filter(
127      d => d.age >= criteria.minAge && d.age <= criteria.maxAge
128    );
129  }
130}
131
132console.log("Search by name:", search({ type: "name", name: "Blue" }));
133console.log("Search by species:", search({ type: "species", species: "Velociraptor" }));
134console.log("Search by age:", search({ type: "age", minAge: 3, maxAge: 4 }));
135
136// ===========================================
137// 5. Overloading methods in classes
138// ===========================================
139console.log("\n=== METHODS IN CLASSES ===");
140
141class DinosaurManager {
142  private dinosaurs: Dinosaur[] = [];
143
144  // Overloads
145  add(dino: Dinosaur): void;
146  add(name: string, species: string, age: number): void;
147
148  // Implementation
149  add(
150    dinoOrName: Dinosaur | string,
151    species?: string,
152    age?: number
153  ): void {
154    if (typeof dinoOrName === "string") {
155      this.dinosaurs.push({
156        name: dinoOrName,
157        species: species!,
158        age: age!
159      });
160    } else {
161      this.dinosaurs.push(dinoOrName);
162    }
163  }
164
165  getAll(): Dinosaur[] {
166    return [...this.dinosaurs];
167  }
168
169  // Overloads get
170  get(index: number): Dinosaur | undefined;
171  get(name: string): Dinosaur | undefined;
172
173  // Implementation
174  get(indexOrName: number | string): Dinosaur | undefined {
175    if (typeof indexOrName === "number") {
176      return this.dinosaurs[indexOrName];
177    } else {
178      return this.dinosaurs.find(d => d.name === indexOrName);
179    }
180  }
181}
182
183const manager = new DinosaurManager();
184manager.add({ name: "Rexy", species: "T-Rex", age: 5 });
185manager.add("Blue", "Velociraptor", 3);
186
187console.log("Get by index:", manager.get(0));
188console.log("Get by name:", manager.get("Blue"));
189
190// ===========================================
191// 6. Overloading with Generics
192// ===========================================
193console.log("\n=== GENERICS WITH OVERLOADING ===");
194
195// Overloads
196function process<T extends string>(value: T): string;
197function process<T extends number>(value: T): number;
198function process<T extends boolean>(value: T): boolean;
199
200// Implementation
201function process<T extends string | number | boolean>(
202  value: T
203): string | number | boolean {
204  if (typeof value === "string") {
205    return value.toUpperCase();
206  } else if (typeof value === "number") {
207    return value * 2;
208  } else {
209    return !value;
210  }
211}
212
213console.log("Process string:", process("hello"));
214console.log("Process number:", process(21));
215console.log("Process boolean:", process(true));
216
217// ===========================================
218// 7. Advanced overloading
219// ===========================================
220console.log("\n=== ADVANCED OVERLOADING ===");
221
222interface FeedOptions {
223  type: "scheduled";
224  time: Date;
225}
226
227interface EmergencyFeedOptions {
228  type: "emergency";
229  reason: string;
230}
231
232// Overloads
233function feedDinosaur(
234  dinosaur: Dinosaur,
235  amount: number
236): void;
237
238function feedDinosaur(
239  dinosaur: Dinosaur,
240  amount: number,
241  options: FeedOptions
242): void;
243
244function feedDinosaur(
245  dinosaur: Dinosaur,
246  amount: number,
247  options: EmergencyFeedOptions
248): void;
249
250// Implementation
251function feedDinosaur(
252  dinosaur: Dinosaur,
253  amount: number,
254  options?: FeedOptions | EmergencyFeedOptions
255): void {
256  if (!options) {
257    console.log(`Feeding ${dinosaur.name} with ${amount}kg`);
258  } else if (options.type === "scheduled") {
259    console.log(`Scheduled feeding for ${dinosaur.name} at ${options.time.toISOString()}`);
260  } else {
261    console.log(`Emergency feeding for ${dinosaur.name}: ${options.reason}`);
262  }
263}
264
265const trex = { name: "Rexy", species: "T-Rex", age: 5 };
266
267feedDinosaur(trex, 400);
268feedDinosaur(trex, 400, { type: "scheduled", time: new Date() });
269feedDinosaur(trex, 600, { type: "emergency", reason: "Very hungry" });
270
271// ===========================================
272// 8. Constructor overloading
273// ===========================================
274console.log("\n=== CONSTRUCTORS ===");
275
276class Paddock {
277  id: string;
278  name: string;
279  capacity: number;
280
281  // Constructor overloads (syntactically)
282  constructor(id: string);
283  constructor(id: string, name: string);
284  constructor(id: string, name: string, capacity: number);
285
286  // Implementation
287  constructor(id: string, name?: string, capacity?: number) {
288    this.id = id;
289    this.name = name || `Paddock ${id}`;
290    this.capacity = capacity || 10;
291  }
292
293  getInfo(): string {
294    return `${this.name} (ID: ${this.id}, Capacity: ${this.capacity})`;
295  }
296}
297
298const paddock1 = new Paddock("P-01");
299const paddock2 = new Paddock("P-02", "T-Rex Territory");
300const paddock3 = new Paddock("P-03", "Raptor Pen", 5);
301
302console.log(paddock1.getInfo());
303console.log(paddock2.getInfo());
304console.log(paddock3.getInfo());
305
306// ===========================================
307// 9. Overloading with Promise
308// ===========================================
309console.log("\n=== OVERLOADING WITH PROMISE ===");
310
311// Overloads
312function fetchData(sync: true): Dinosaur;
313function fetchData(sync: false): Promise<Dinosaur>;
314
315// Implementation
316function fetchData(sync: boolean): Dinosaur | Promise<Dinosaur> {
317  const dino: Dinosaur = {
318    name: "Rexy",
319    species: "T-Rex",
320    age: 5
321  };
322
323  if (sync) {
324    return dino;
325  } else {
326    return Promise.resolve(dino);
327  }
328}
329
330const syncData = fetchData(true);
331console.log("Sync data:", syncData);
332
333fetchData(false).then(asyncData => {
334  console.log("Async data:", asyncData);
335});
336
337// ===========================================
338// 10. Summary
339// ===========================================
340console.log("\n=== SUMMARY ===");
341console.log("Basic overloading - different signatures");
342console.log("Different parameters - optional arguments");
343console.log("Different return types - the type depends on the parameters");
344console.log("Objects - discriminated unions");
345console.log("Methods in classes - method overloading");
346console.log("Generics - overloading with generic types");
347console.log("Constructors - constructor overloading");
348console.log("Promise - synchronous vs asynchronous");
349
350console.log("\nFunction overloading mastered!");

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is function overloading in TypeScript?

  2. 2. How many implementation signatures can an overloaded function have in TypeScript?

These are 2 of 4 questions for this lesson. Solve the rest in the game.

Hands-on tasks in the game

  • Code editor

    Create an overloaded findDino function that accepts an id (number) or name (string) and returns the appropriate result.

  • Click in order

    Arrange the elements of an overloaded function in TypeScript:

  • Code editor

    Create a function that takes a parameter of type string | number and uses typeof as a type guard to return the appropriate dinosaur description.

  • Vertical ordering

    Order interfaces from most general (base) to most specific (extending):

  • Click in order

    Arrange the elements of the overloaded findDino function declaration in TypeScript:

Useful articles