Functions & Overloads
๐ Functions & Overloads
๐ง Function Typing Basics (Review)
Parameter Types and Return Types
function greet(name: string): string { return `Hello, ${name}!`; } function add(a: number, b: number): number { return a + b; } // Arrow function syntax const multiply = (a: number, b: number): number => a * b; // Optional parameters function greetOptional(name: string, title?: string): string { return title ? `${title} ${name}` : name; } // Rest parameters function sum(...numbers: number[]): number { return numbers.reduce((a, b) => a + b, 0); }
๐ฏ Function Overloads
The Pattern
// Overload signatures (just the type, no body) function describe(value: string): string; function describe(value: number): string; // Implementation (with union type to match all overloads) function describe(value: string | number): string { if (typeof value === "string") { return `String: ${value}`; } else { return `Number: ${value.toFixed(2)}`; } } // Callers see the specific signatures describe("hello"); // โ Matches first signature describe(42); // โ Matches second signature describe(true); // โ Error! No overload for boolean
Overloads with Different Return Types
// Each overload can have a different return type function process(input: string): string; function process(input: number): number; function process(input: boolean): boolean; function process(input: string | number | boolean) { if (typeof input === "string") { return input.toUpperCase(); } else if (typeof input === "number") { return input * 2; } else { return !input; } } const str: string = process("hello"); // TypeScript knows return is string const num: number = process(5); // TypeScript knows return is number const bool: boolean = process(true); // TypeScript knows return is boolean
Overloads with Optional Parameters
// Different number of parameters function createElement(tag: string): HTMLElement; function createElement(tag: string, content: string): HTMLElement; function createElement(tag: string, content: string, className: string): HTMLElement; function createElement( tag: string, content?: string, className?: string ): HTMLElement { const el = document.createElement(tag); if (content) el.textContent = content; if (className) el.className = className; return el; } createElement("div"); // โ Just tag createElement("div", "Hello"); // โ Tag + content createElement("div", "Hello", "container"); // โ All three
๐ค Overloads vs Union Types
When should you use overloads instead of union types? Let's compare:
function padString( value: string | number ): string { return String(value).padStart(10); } const result = padString(42); // TypeScript doesn't know result is a string // The caller loses type information!
function padString(value: string): string; function padString(value: number): string; function padString(value: string | number) { return String(value).padStart(10); } const result = padString(42); // TypeScript knows result is a string! // Overloads preserve type information
When to Use Overloads
- Different return types โ Based on input type, return type differs
- Different parameters โ Accept different number of parameters
- Complex relationships โ Type of parameter 2 depends on parameter 1
- API clarity โ Make intent clear to callers
When NOT to Use Overloads
- Same behavior โ If logic is identical, just use union types
- Simple cases โ Optional parameters are often enough
- Too many overloads โ More than 3-4 usually signals design problem
๐ Advanced Overload Patterns
Overloads with Generics
// Generic function with overloads function getProperty<T>(obj: T, key: string): unknown; function getProperty<T, K extends keyof T>(obj: T, key: K): T[K]; function getProperty<T>(obj: T, key: string) { return obj[key as keyof T]; } interface User { name: string; age: number; } const user: User = { name: "Alice", age: 30 }; // โ TypeScript knows this returns a string const name: string = getProperty(user, "name"); // โ TypeScript knows this returns a number const age: number = getProperty(user, "age");
Overloads with Conditional Return Types
// Using conditional types with overloads function flatten<T>(arr: T[]): T[]; function flatten<T>(arr: T[][]): T[]; function flatten<T>(arr: T[] | T[][]): T[] { return arr.flat() as T[]; } const single: string[] = flatten(["a", "b"]); const double: number[] = flatten([[1, 2], [3, 4]]);
๐ Best Practices
โ DO: Overloads for Clearer Intent
When different input types produce different return types, overloads document this relationship:
function parse(input: string): Record<string, unknown>; function parse(input: Buffer): Uint8Array; function parse(input: string | Buffer) { // implementation }
โ DON'T: Too Many Overloads
More than 3-4 overloads often means your function is doing too much:
// โ Too many overloads - refactor instead function process(input: string): string; function process(input: number): number; function process(input: boolean): boolean; function process(input: Date): string; function process(input: object): object;
If the overload signatures are hard to understand, the function signature might be too complex. Consider breaking it into multiple, simpler functions instead.
๐ป Coding Challenges
Challenge 1: Basic Function Overload
Create a function that accepts either a string or a number and returns a boolean. If string, check if length > 5. If number, check if > 100.
- Define two overload signatures
- Write the implementation with both cases
- Test with both string and number inputs
Goal: Practice basic overload syntax and type checking.
Challenge 2: Overload with Different Return Types
Create a convertToJSON function that takes an object and optional formatting. If formatting is true, return pretty JSON. If false, return compact JSON.
- Define overloads for formatted/unformatted
- Both return strings (different content)
- Use JSON.stringify with appropriate parameters
Goal: Practice conditional behavior with overloads.
Challenge 3: Generic Overload
Create a wrapInArray function that takes a single value OR an array, and always returns an array. If passed a single value, wrap it. If passed an array, return it as-is.
- Define overloads for single value and array
- Use generics to preserve the element type
- Ensure correct array nesting
Goal: Practice generic overloads and type preservation.
๐ Real-World Example: Event Listener
// Realistic addEventListener with proper typing function addEventListener( element: HTMLElement, event: "click", handler: (e: MouseEvent) => void ): void; function addEventListener( element: HTMLElement, event: "input", handler: (e: InputEvent) => void ): void; function addEventListener( element: HTMLElement, event: "click" | "input", handler: (e: Event) => void ) { element.addEventListener(event, handler); } // Callers get proper type checking for each event const button = document.querySelector("button")!; const input = document.querySelector("input")!; addEventListener(button, "click", (e: MouseEvent) => { // โ e is typed as MouseEvent console.log(e.clientX); }); addEventListener(input, "input", (e: InputEvent) => { // โ e is typed as InputEvent console.log(e.data); });
๐ฏ What's Next
We've mastered function overloads and learned when to use them. Chapter 9 covers Enums & Literal Types โ how to create strongly-typed constants and restrict values to specific options.