JavaScript and TypeScript course Β· Module 6: TypeScript Basics
Function Overloading
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:
- Overload signatures - definitions of parameter types and return values
- 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 typeIn the above example:
- We defined two overload signatures - one accepting
number, another acceptingstring - We created one implementation that handles both types
- 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); // OKNote 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 objectMethod 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 sizeAdvanced 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 GatunekBest 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:
- Define overload signatures before the implementation signature
- The implementation signature must be compatible with all overload signatures
- Place more specific signatures before more general ones
- Use overloads when you want different return types for different input parameters
- 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. What is function overloading in TypeScript?
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: