๐ŸŽฏ Function Overloads

TypeScript Fundamentals โ€” Functions & Overloads
TypeScript Fundamentals
Course 1 ยท Chapter 8 ยท Functions & Overloads

๐Ÿ“ž Functions & Overloads

You've written typed functions. Now we'll explore how TypeScript handles functions that can behave differently based on their arguments. Function overloads let you define multiple signatures for a single function, making it type-safe and self-documenting.

๐Ÿ”ง 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

A function overload lets you define multiple signatures for the same function. The actual implementation comes after all the signatures. TypeScript uses these signatures to type-check calls to the function.

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
โœ… Key Point: The overload signatures are for type-checking. The implementation uses a union type that covers all cases.

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:

โŒ Union Type (Bad)
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!
โœ… Overloads (Good)
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;
๐Ÿ’ก Tip: Overloads Should Be Simple

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.

โ†’ Solution

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.

โ†’ Solution

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.

โ†’ Solution

๐ŸŒ 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.