Skip to main content
Practice Problems

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

typescript
// 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

typescript
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

Optional Parameters with Different Types

typescript
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.

typescript
// 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.

typescript
// 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

typescript
// 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

typescript
// Union type is simpler and clearer function format(value: string | number): string { return value.toString(); }

Practical Examples

Array Extraction

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
// 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

typescript
// 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

typescript
// 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

typescript
// 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

typescript
// 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

typescript
/** * 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

typescript
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

typescript
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)

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems