"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.
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.
In TypeScript, function overloading consists of two parts:
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:
number, another accepting stringThe 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.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 objectFunction 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 size1// 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"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 GatunekParameters 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}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}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}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} ===
164`;
165 raport += `Date: ${new Date().toLocaleString()}
166`;
167 raport += `Number of dinosaurs: ${dinosaury.length}
168
169`;
170
171 dinosaury.forEach(d => {
172 raport += `ID: ${d.id} | ${d.nazwa} | ${d.species} | Status: ${d.status} | Threat: ${d.threatLevel}
173`;
174 });
175
176 return raport;
177 }
178}
179
180// Example usage of the system
181const system = new DinosaurManagementSystem();
182
183// Adding dinosaurs with different data sets
184const rexId = system.dodajDinosaura({
185 id: 1,
186 nazwa: "Rexy",
187 species: "Tyrannosaurus",
188 status: "zdrowy",
189 threatLevel: 5
190});
191
192const raptorId = system.dodajDinosaura(
193 {
194 id: 2,
195 nazwa: "Blue",
196 species: "Velociraptor",
197 status: "zdrowy",
198 threatLevel: 4
199 },
200 {
201 bodyTemperature: 38.5,
202 heartRate: 85,
203 czynnikStresowy: 2.1,
204 lastMeal: new Date(Date.now() - 3600000), // 1 hour ago
205 wagaKg: 160
206 }
207);
208
209const triceratopsId = system.dodajDinosaura(
210 {
211 id: 3,
212 nazwa: "Tricy",
213 species: "Triceratops",
214 status: "zdrowy",
215 threatLevel: 2
216 },
217 {
218 bodyTemperature: 37.2,
219 heartRate: 45,
220 czynnikStresowy: 1.3,
221 lastMeal: new Date(Date.now() - 7200000), // 2 hours ago
222 wagaKg: 8500
223 },
224 {
225 sektor: "B-7",
226 gpsCoordinates: [34.5678, -118.2345],
227 ostatniaAktualizacja: new Date()
228 }
229);
230
231// Retrieving different types of data
232const rexPodstawowe = system.pobierzDane(rexId);
233console.log(`Dinosaur ${rexPodstawowe.nazwa} has threat level ${rexPodstawowe.threatLevel}`);
234
235const raptorZdrowie = system.pobierzDane(raptorId, "zdrowie");
236if (raptorZdrowie) {
237 console.log(`Body temperature of raptor: ${raptorZdrowie.bodyTemperature}°C`);
238}
239
240const triceratopsLokalizacja = system.pobierzDane(triceratopsId, "lokalizacja");
241if (triceratopsLokalizacja) {
242 console.log(`Triceratops is located in sector: ${triceratopsLokalizacja.sektor}`);
243}
244
245// Updating data
246system.aktualizujDane(rexId, { status: "w leczeniu" });
247
248system.aktualizujDane(raptorId,
249 {
250 bodyTemperature: 39.2,
251 heartRate: 95,
252 czynnikStresowy: 4.5,
253 lastMeal: new Date(),
254 wagaKg: 162
255 },
256 "zdrowie"
257);
258
259// Generating reports
260console.log(system.generujRaport());
261console.log(system.generujRaport("Velociraptor"));
262console.log(system.generujRaport(5));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:
"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."