Back to Blog
TypeScriptFunctionsOverloadsType Safety

TypeScript Function Overloads Guide

Master TypeScript function overloads for type-safe functions with multiple call signatures.

B
Bootspring Team
Engineering
November 3, 2019
6 min read

Function overloads define multiple call signatures for a single function. Here's how to use them effectively.

Basic Syntax#

1// Overload signatures (declarations) 2function greet(name: string): string; 3function greet(name: string, age: number): string; 4 5// Implementation signature 6function greet(name: string, age?: number): string { 7 if (age !== undefined) { 8 return `Hello, ${name}! You are ${age} years old.`; 9 } 10 return `Hello, ${name}!`; 11} 12 13// Usage 14greet('Alice'); // OK 15greet('Bob', 30); // OK 16greet('Charlie', 'old'); // Error: no matching overload

Different Return Types#

1// Return type depends on input 2function parse(input: string): string[]; 3function parse(input: number): number[]; 4function parse(input: string | number): string[] | number[] { 5 if (typeof input === 'string') { 6 return input.split(','); 7 } 8 return [input]; 9} 10 11const strings = parse('a,b,c'); // string[] 12const numbers = parse(42); // number[] 13 14// More complex return types 15function createElement(tag: 'div'): HTMLDivElement; 16function createElement(tag: 'span'): HTMLSpanElement; 17function createElement(tag: 'input'): HTMLInputElement; 18function createElement(tag: string): HTMLElement { 19 return document.createElement(tag); 20} 21 22const div = createElement('div'); // HTMLDivElement 23const span = createElement('span'); // HTMLSpanElement 24const input = createElement('input'); // HTMLInputElement

Object Parameter Overloads#

1interface UserById { 2 id: number; 3} 4 5interface UserByEmail { 6 email: string; 7} 8 9function findUser(criteria: UserById): Promise<User>; 10function findUser(criteria: UserByEmail): Promise<User>; 11function findUser(criteria: UserById | UserByEmail): Promise<User> { 12 if ('id' in criteria) { 13 return db.findById(criteria.id); 14 } 15 return db.findByEmail(criteria.email); 16} 17 18// Usage 19findUser({ id: 123 }); // OK 20findUser({ email: 'a@b.com' }); // OK 21findUser({ id: 1, email: 'a@b.com' }); // OK (matches first overload)

Optional Parameters#

1// Different signatures for optional params 2function format(value: number): string; 3function format(value: number, decimals: number): string; 4function format(value: number, decimals: number, prefix: string): string; 5function format(value: number, decimals?: number, prefix?: string): string { 6 const formatted = decimals !== undefined 7 ? value.toFixed(decimals) 8 : value.toString(); 9 10 return prefix ? `${prefix}${formatted}` : formatted; 11} 12 13format(42); // '42' 14format(42.567, 2); // '42.57' 15format(42.567, 2, '$'); // '$42.57'

Callback Signatures#

1// Different callback signatures 2function fetchData(url: string): Promise<unknown>; 3function fetchData(url: string, callback: (data: unknown) => void): void; 4function fetchData( 5 url: string, 6 callback?: (data: unknown) => void 7): Promise<unknown> | void { 8 const promise = fetch(url).then((r) => r.json()); 9 10 if (callback) { 11 promise.then(callback); 12 return; 13 } 14 15 return promise; 16} 17 18// Promise-based 19const data = await fetchData('/api/data'); 20 21// Callback-based 22fetchData('/api/data', (data) => { 23 console.log(data); 24});

Generic Overloads#

1// Generic overloads 2function first<T>(arr: T[]): T | undefined; 3function first<T>(arr: T[], defaultValue: T): T; 4function first<T>(arr: T[], defaultValue?: T): T | undefined { 5 return arr.length > 0 ? arr[0] : defaultValue; 6} 7 8const num = first([1, 2, 3]); // number | undefined 9const withDefault = first([], 0); // number 10 11// Multiple generics 12function merge<T>(target: T, source: Partial<T>): T; 13function merge<T, U>(target: T, source: U): T & U; 14function merge(target: any, source: any) { 15 return { ...target, ...source }; 16} 17 18const result1 = merge({ a: 1 }, { a: 2 }); // { a: number } 19const result2 = merge({ a: 1 }, { b: 'hello' }); // { a: number } & { b: string }

Method Overloads#

1class Calculator { 2 // Method overloads 3 add(a: number, b: number): number; 4 add(a: string, b: string): string; 5 add(a: number | string, b: number | string): number | string { 6 if (typeof a === 'number' && typeof b === 'number') { 7 return a + b; 8 } 9 return String(a) + String(b); 10 } 11} 12 13const calc = new Calculator(); 14const sum = calc.add(1, 2); // number 15const concat = calc.add('a', 'b'); // string

Event Handler Overloads#

1interface ClickEvent { 2 type: 'click'; 3 x: number; 4 y: number; 5} 6 7interface KeyEvent { 8 type: 'keydown'; 9 key: string; 10} 11 12function handleEvent(event: ClickEvent): void; 13function handleEvent(event: KeyEvent): void; 14function handleEvent(event: ClickEvent | KeyEvent): void { 15 if (event.type === 'click') { 16 console.log(`Click at ${event.x}, ${event.y}`); 17 } else { 18 console.log(`Key pressed: ${event.key}`); 19 } 20} 21 22// Type-safe event handling 23handleEvent({ type: 'click', x: 100, y: 200 }); 24handleEvent({ type: 'keydown', key: 'Enter' });

API Response Types#

1interface User { 2 id: number; 3 name: string; 4} 5 6interface Post { 7 id: number; 8 title: string; 9} 10 11function fetch(endpoint: '/users'): Promise<User[]>; 12function fetch(endpoint: '/users', id: number): Promise<User>; 13function fetch(endpoint: '/posts'): Promise<Post[]>; 14function fetch(endpoint: '/posts', id: number): Promise<Post>; 15function fetch(endpoint: string, id?: number): Promise<unknown> { 16 const url = id ? `${endpoint}/${id}` : endpoint; 17 return globalThis.fetch(url).then((r) => r.json()); 18} 19 20const users = await fetch('/users'); // User[] 21const user = await fetch('/users', 1); // User 22const posts = await fetch('/posts'); // Post[] 23const post = await fetch('/posts', 1); // Post

Constructor Overloads#

1class DateRange { 2 start: Date; 3 end: Date; 4 5 // Constructor overloads 6 constructor(start: Date, end: Date); 7 constructor(range: { start: Date; end: Date }); 8 constructor(startOrRange: Date | { start: Date; end: Date }, end?: Date) { 9 if (startOrRange instanceof Date) { 10 this.start = startOrRange; 11 this.end = end!; 12 } else { 13 this.start = startOrRange.start; 14 this.end = startOrRange.end; 15 } 16 } 17} 18 19new DateRange(new Date(), new Date()); 20new DateRange({ start: new Date(), end: new Date() });

Interface Method Overloads#

1interface StringTransformer { 2 (input: string): string; 3 (input: string[]): string[]; 4} 5 6const uppercase: StringTransformer = (input: string | string[]) => { 7 if (Array.isArray(input)) { 8 return input.map((s) => s.toUpperCase()); 9 } 10 return input.toUpperCase(); 11}; 12 13const single = uppercase('hello'); // string 14const multiple = uppercase(['a', 'b']); // string[]

Narrowing with Overloads#

1// Use overloads for better narrowing 2function getValue(key: 'name'): string; 3function getValue(key: 'age'): number; 4function getValue(key: 'active'): boolean; 5function getValue(key: string): unknown { 6 const values = { 7 name: 'Alice', 8 age: 30, 9 active: true, 10 }; 11 return values[key as keyof typeof values]; 12} 13 14const name = getValue('name'); // string 15const age = getValue('age'); // number 16const active = getValue('active'); // boolean

Best Practices#

Design: ✓ Order overloads specific to general ✓ Keep implementation signature broad ✓ Document each overload ✓ Use for distinct behaviors Patterns: ✓ Different return types ✓ Optional callback vs Promise ✓ Type narrowing by parameter ✓ Builder pattern APIs Avoid: ✗ Too many overloads (> 4-5) ✗ Overloads for default values ✗ Complex conditional returns ✗ Overloads that could be unions Alternatives: ✓ Union types when simpler ✓ Generics for type inference ✓ Conditional types for complex cases ✓ Separate functions for clarity

Conclusion#

Function overloads provide multiple type-safe ways to call a function. Use them when the return type depends on input types, or when different parameter combinations have distinct behaviors. Order overloads from most specific to least specific, and keep the implementation signature compatible with all overloads.

Share this article

Help spread the word about Bootspring