Function Overloads in TypeScript
What are Function Overloads?
Function Overloads are the ability to declare multiple signatures for a single function. TypeScript will choose the correct signature based on the passed arguments.
Basic Syntax
// Overload signatures
function greet(name: string): string;
function greet(firstName: string, lastName: string): string;
// Implementation signature
function greet(firstName: string, lastName?: string): string {
if (lastName) {
return `Hello, ${firstName} ${lastName}!`;
}
return `Hello, ${firstName}!`;
}
// Usage
greet('John'); // "Hello, John!"
greet('John', 'Doe'); // "Hello, John Doe!"
// greet('John', 'Doe', 'Jr.'); // Error: Expected 1-2 arguments
Why Use Overloads?
Different Return Types
function getValue(key: 'name'): string;
function getValue(key: 'age'): number;
function getValue(key: 'active'): boolean;
function getValue(key: string): string | number | boolean {
const data = {
name: 'John',
age: 30,
active: true
};
return data[key as keyof typeof data];
}
const name = getValue('name'); // string
const age = getValue('age'); // number
const active = getValue('active'); // boolean
Related Argument and Return Types
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'button'): HTMLButtonElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
const div = createElement('div'); // HTMLDivElement
const span = createElement('span'); // HTMLSpanElement
const button = createElement('button'); // HTMLButtonElement
Optional Parameters with Different Types
function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
if (month !== undefined && day !== undefined) {
return new Date(yearOrTimestamp, month, day);
}
return new Date(yearOrTimestamp);
}
const date1 = makeDate(1234567890); // Date
const date2 = makeDate(2024, 11, 19); // Date
// const date3 = makeDate(2024, 11); // Error
Signature Order Matters
TypeScript checks signatures top-to-bottom and uses the first matching one.
// Wrong - general signature first
function process(value: any): any;
function process(value: string): string;
function process(value: number): number;
function process(value: any): any {
return value;
}
const result = process('hello'); // any (not string!)
// Correct - from specific to general
function process(value: string): string;
function process(value: number): number;
function process(value: any): any;
function process(value: any): any {
return value;
}
const result = process('hello'); // string
Implementation Signature
Implementation must be compatible with ALL overload signatures.
// Overload signatures
function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
// Implementation must cover both cases
function combine(a: string | number, b: string | number): string | number {
if (typeof a === 'string' && typeof b === 'string') {
return a + b;
}
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
throw new Error('Invalid arguments');
}
const str = combine('Hello, ', 'World'); // string
const num = combine(10, 20); // number
Important:
Implementation signature is NOT visible externally. Users only see overload signatures.
Overloads vs Union Types
When to Use Overloads
// With overloads - type depends on argument
function parse(data: string): object;
function parse(data: object): string;
function parse(data: string | object): string | object {
if (typeof data === 'string') {
return JSON.parse(data);
}
return JSON.stringify(data);
}
const obj = parse('{"name":"John"}'); // object
const str = parse({ name: 'John' }); // string
When Union Types Suffice
// Union type is simpler and clearer
function format(value: string | number): string {
return value.toString();
}
Practical Examples
Array Extraction
function getFirst<T>(arr: T[]): T | undefined;
function getFirst<T>(arr: T[], count: number): T[];
function getFirst<T>(arr: T[], count?: number): T | T[] | undefined {
if (count === undefined) {
return arr[0];
}
return arr.slice(0, count);
}
const numbers = [1, 2, 3, 4, 5];
const first = getFirst(numbers); // number | undefined
const firstThree = getFirst(numbers, 3); // number[]
Finding Elements
function find(predicate: (item: string) => boolean): string | undefined;
function find(predicate: (item: string) => boolean, all: true): string[];
function find(
predicate: (item: string) => boolean,
all?: boolean
): string | string[] | undefined {
const items = ['apple', 'banana', 'cherry'];
if (all) {
return items.filter(predicate);
}
return items.find(predicate);
}
const one = find(item => item.startsWith('a')); // string | undefined
const many = find(item => item.startsWith('a'), true); // string[]
Event Listener
function addEventListener(
element: HTMLElement,
event: 'click',
handler: (e: MouseEvent) => void
): void;
function addEventListener(
element: HTMLElement,
event: 'keypress',
handler: (e: KeyboardEvent) => void
): void;
function addEventListener(
element: HTMLElement,
event: string,
handler: (e: Event) => void
): void {
element.addEventListener(event, handler);
}
const button = document.querySelector('button')!;
addEventListener(button, 'click', (e) => {
// e: MouseEvent
console.log(e.clientX);
});
addEventListener(button, 'keypress', (e) => {
// e: KeyboardEvent
console.log(e.key);
});
API Wrapper
function request(method: 'GET', url: string): Promise<Response>;
function request(
method: 'POST' | 'PUT',
url: string,
body: object
): Promise<Response>;
function request(
method: string,
url: string,
body?: object
): Promise<Response> {
const options: RequestInit = { method };
if (body) {
options.body = JSON.stringify(body);
options.headers = { 'Content-Type': 'application/json' };
}
return fetch(url, options);
}
// Usage
request('GET', '/api/users');
request('POST', '/api/users', { name: 'John' });
// request('POST', '/api/users'); // Error: body required
Class Method Overloads
class DataStore {
private data: Record<string, any> = {};
// Method overloads
get(key: 'name'): string;
get(key: 'age'): number;
get(key: 'active'): boolean;
get(key: string): any {
return this.data[key];
}
set(key: 'name', value: string): void;
set(key: 'age', value: number): void;
set(key: 'active', value: boolean): void;
set(key: string, value: any): void {
this.data[key] = value;
}
}
const store = new DataStore();
store.set('name', 'John'); // OK
store.set('age', 30); // OK
// store.set('age', 'thirty'); // Error
const name = store.get('name'); // string
const age = store.get('age'); // number
Constructor Overloads
class Point {
x: number;
y: number;
constructor(x: number, y: number);
constructor(coords: { x: number; y: number });
constructor(xOrCoords: number | { x: number; y: number }, y?: number) {
if (typeof xOrCoords === 'number') {
this.x = xOrCoords;
this.y = y!;
} else {
this.x = xOrCoords.x;
this.y = xOrCoords.y;
}
}
}
const p1 = new Point(10, 20);
const p2 = new Point({ x: 10, y: 20 });
Generics in Overloads
function map<T, U>(arr: T[], fn: (item: T) => U): U[];
function map<T, U>(arr: T[], fn: (item: T, index: number) => U): U[];
function map<T, U>(
arr: T[],
fn: (item: T, index?: number) => U
): U[] {
return arr.map(fn as any);
}
const numbers = [1, 2, 3];
const doubled = map(numbers, n => n * 2);
const indexed = map(numbers, (n, i) => `${i}: ${n}`);
Common Mistakes
Incompatible Implementation
// Error: implementation not compatible with overloads
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean { // Error!
return value;
}
Forgetting Order
// Bad - general case hides specific ones
function handle(value: any): any;
function handle(value: string): string; // Unreachable!
function handle(value: any): any {
return value;
}
Too Many Overloads
// Bad - too complex
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: Date): string;
function format(value: object): string;
// ... 10 more overloads
// Better use union or generic
function format(value: string | number | boolean | Date | object): string {
return String(value);
}
Best Practices
Use Overloads Only When Necessary
// Overloads not needed
function add(a: number, b: number): number {
return a + b;
}
// Overloads needed - different result types
function getValue(key: 'name'): string;
function getValue(key: 'age'): number;
function getValue(key: string): any {
// ...
}
Keep Overloads Close to Implementation
// Good - all together
function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
return value;
}
Document Complex Overloads
/**
* Creates a date from timestamp
* @param timestamp - Unix timestamp in milliseconds
*/
function makeDate(timestamp: number): Date;
/**
* Creates a date from year, month, and day
* @param year - Full year (e.g., 2024)
* @param month - Month (0-11)
* @param day - Day of month (1-31)
*/
function makeDate(year: number, month: number, day: number): Date;
function makeDate(yearOrTimestamp: number, month?: number, day?: number): Date {
// ...
}
Alternatives to Overloads
Conditional Types
type ReturnType<T> = T extends 'string' ? string :
T extends 'number' ? number :
T extends 'boolean' ? boolean :
never;
function getValue<T extends 'string' | 'number' | 'boolean'>(
type: T
): ReturnType<T> {
// ...
return null as any;
}
Discriminated Unions
type Options =
| { type: 'GET'; url: string }
| { type: 'POST'; url: string; body: object };
function request(options: Options): Promise<Response> {
// ...
}
Conclusion
Function Overloads:
- Allow declaring multiple signatures for one function
- Signature choice depends on arguments
- Order matters (from specific to general)
- Implementation must be compatible with all overloads
- Useful when return type depends on arguments
- Alternatives: union types, conditional types, discriminated unions
- Don't overuse — use only when necessary
In Interviews:
Important to be able to:
- Explain function overload syntax
- Write example with different return types
- Explain difference between overload and implementation signatures
- Show importance of signature order
- Tell when overloads are better than union types
- Name alternatives (conditional types, discriminated unions)