Fix: TypeScript Function Overload Error — No Overload Matches This Call
Quick Answer
How to fix TypeScript function overload errors — overload signature compatibility, implementation signature, conditional types as alternatives, method overloads in classes, and common pitfalls.
The Problem
TypeScript rejects a valid call to an overloaded function:
function format(value: string): string;
function format(value: number, decimals: number): string;
function format(value: string | number, decimals?: number): string {
if (typeof value === 'string') return value.toUpperCase();
return value.toFixed(decimals ?? 2);
}
format('hello'); // OK
format(3.14159, 2); // OK
format(3.14159); // Error: No overload matches this call.
// Overload 2 requires 2 arguments, but got 1.Or the implementation signature is accidentally exposed:
function process(data: string): number;
function process(data: number): string;
function process(data: string | number): string | number {
// ...
}
process('test' as string | number); // Error — implementation signature not callableOr overloads in a class method conflict:
class Converter {
convert(value: string): number;
convert(value: number): string;
convert(value: string | number): string | number {
return typeof value === 'string' ? parseInt(value) : String(value);
}
}
const c = new Converter();
c.convert('42'); // OK
c.convert(42); // OK
c.convert(someUnion); // Error: No overload matches this callWhy This Happens
TypeScript function overloads have a counterintuitive rule: the implementation signature is invisible to callers. Only the overload signatures (the ones before the implementation) define what callers can pass.
Common mistakes:
- Implementation signature not covered by overloads — if callers should be able to pass
string | number, you need an explicit overload for that union. The implementation’sstring | numberparameter isn’t callable directly. - Missing overloads for valid input combinations —
format(3.14159)(one argument) isn’t covered because the onlynumberoverload requires two arguments. - Overload signatures incompatible with implementation — TypeScript checks that all overload signatures are assignable to the implementation signature. If they’re not, the implementation can’t handle all cases the overloads promise.
- Overly narrow implementation signature — the implementation must accept a superset of all overload signatures. If an overload accepts
nullbut the implementation doesn’t, TypeScript errors.
Fix 1: Add Missing Overloads
Add an overload signature for every combination of arguments callers might use:
// WRONG — no overload for format(number) without decimals
function format(value: string): string;
function format(value: number, decimals: number): string; // Requires 2nd arg
function format(value: string | number, decimals?: number): string {
if (typeof value === 'string') return value.toUpperCase();
return value.toFixed(decimals ?? 2);
}
format(3.14); // Error: No overload matches (number without decimals)
// CORRECT — add the missing overload
function format(value: string): string;
function format(value: number): string; // Added: number with optional decimals
function format(value: number, decimals: number): string;
function format(value: string | number, decimals?: number): string {
if (typeof value === 'string') return value.toUpperCase();
return value.toFixed(decimals ?? 2);
}
format('hello'); // Uses overload 1
format(3.14); // Uses overload 2
format(3.14, 4); // Uses overload 3Order overloads from most specific to least specific:
// BEST PRACTICE — specific overloads first, general last
function createElement(tag: 'canvas'): HTMLCanvasElement;
function createElement(tag: 'img'): HTMLImageElement;
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: string): HTMLElement; // Catch-all last
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
createElement('canvas').getContext('2d'); // Returns HTMLCanvasElement
createElement('img').src = '/image.png'; // Returns HTMLImageElement
createElement('div').style.color = 'red'; // Returns HTMLElement (catch-all)Fix 2: Make the Implementation Signature Broad Enough
The implementation signature must be compatible with all overload signatures:
// WRONG — implementation signature too narrow
function greet(name: string): string;
function greet(name: string, greeting: string): string;
function greet(name: string): string { // Missing 'greeting' parameter
return `Hello, ${name}`;
}
// CORRECT — implementation matches all overloads
function greet(name: string): string;
function greet(name: string, greeting: string): string;
function greet(name: string, greeting?: string): string { // Optional second param
return `${greeting ?? 'Hello'}, ${name}`;
}
// WRONG — return type too specific in implementation
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string | number): string { // Return type doesn't cover number
// ...
}
// CORRECT — implementation return type covers all overload return types
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string | number): string | number { // Union of all return types
return typeof input === 'string' ? parseInt(input) : String(input);
}Fix 3: Use Conditional Types as a Cleaner Alternative
For type-safe overloads based on input type, conditional types can replace overload signatures:
// Overload approach — verbose
function parse(input: string): number;
function parse(input: number): string;
function parse(input: string | number): string | number {
return typeof input === 'string' ? parseInt(input) : String(input);
}
// Conditional type approach — cleaner and handles unions correctly
type ParseResult<T> = T extends string ? number : T extends number ? string : never;
function parse<T extends string | number>(input: T): ParseResult<T> {
return (typeof input === 'string' ? parseInt(input) : String(input)) as ParseResult<T>;
}
const num = parse('42'); // Type: number
const str = parse(42); // Type: string
const union = parse(value as string | number); // Type: number | string — works!When to use overloads vs conditional types:
- Use overloads when the logic branches are cleanly separated and you have a small number of combinations
- Use conditional types when the return type is a mathematical function of the input type
- Use overloads when different call signatures have very different parameter counts
Fix 4: Fix Class Method Overloads
Class methods follow the same rules as functions:
class HttpClient {
// Method overloads — define public API
get(url: string): Promise<unknown>;
get<T>(url: string): Promise<T>;
get<T>(url: string, options: RequestInit): Promise<T>;
// Implementation — must handle all cases
async get<T = unknown>(url: string, options?: RequestInit): Promise<T> {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json() as T;
}
}
const client = new HttpClient();
client.get('/api/data'); // Returns Promise<unknown>
client.get<User[]>('/api/users'); // Returns Promise<User[]>
client.get<User>('/api/user', { method: 'POST' }); // Returns Promise<User>Interface method overloads:
// Define overloads in the interface
interface Parser {
parse(input: string): number;
parse(input: number): string;
parse(input: boolean): string;
}
// Implementation class must satisfy ALL overload signatures
class DataParser implements Parser {
parse(input: string): number;
parse(input: number): string;
parse(input: boolean): string;
parse(input: string | number | boolean): string | number {
if (typeof input === 'string') return parseInt(input);
return String(input);
}
}Fix 5: Overloads with Optional Parameters
Common pattern — one required parameter with optional extras:
// Build a query string with optional parameters
function buildQuery(base: string): string;
function buildQuery(base: string, params: Record<string, string>): string;
function buildQuery(base: string, params: Record<string, string>, encode: boolean): string;
function buildQuery(
base: string,
params?: Record<string, string>,
encode = true
): string {
if (!params) return base;
const queryString = Object.entries(params)
.map(([k, v]) => `${k}=${encode ? encodeURIComponent(v) : v}`)
.join('&');
return `${base}?${queryString}`;
}
buildQuery('/api/users');
buildQuery('/api/users', { page: '1' });
buildQuery('/api/users', { search: 'hello world' }, false);Avoid redundant overloads for optional parameters:
// REDUNDANT — these three do the same as one signature with optional params
function greet(name: string): void;
function greet(name: string, age: number): void;
function greet(name: string, age?: number): void { ... }
// SIMPLER — single signature with optional parameter
// (When a function's only variation is optional params, skip overloads)
function greet(name: string, age?: number): void {
console.log(age ? `${name}, ${age}` : name);
}Fix 6: Common Overload Anti-Patterns
Anti-pattern: using overloads to simulate union types:
// UNNECESSARY OVERLOAD — just use a union parameter
function log(message: string): void;
function log(message: Error): void;
function log(message: string | Error): void {
console.log(message instanceof Error ? message.message : message);
}
// SIMPLER — union parameter directly
function log(message: string | Error): void {
console.log(message instanceof Error ? message.message : message);
}Anti-pattern: too many overloads instead of generics:
// TOO MANY OVERLOADS — repetitive
function identity(value: string): string;
function identity(value: number): number;
function identity(value: boolean): boolean;
function identity(value: any): any { return value; }
// BETTER — generic preserves type
function identity<T>(value: T): T { return value; }
const s = identity('hello'); // Type: string
const n = identity(42); // Type: numberWhen overloads are genuinely needed:
// GOOD USE OF OVERLOADS — different parameter structures, not just types
function on(event: 'click', handler: (e: MouseEvent) => void): void;
function on(event: 'keydown', handler: (e: KeyboardEvent) => void): void;
function on(event: 'scroll', handler: (e: Event) => void): void;
function on(event: string, handler: (e: Event) => void): void {
document.addEventListener(event, handler);
}
// Type-safe event handler registration
on('click', (e) => {
console.log(e.clientX, e.clientY); // e is MouseEvent — correct properties
});Fix 7: Debug Overload Resolution
TypeScript tries overloads in order and uses the first match:
// TypeScript checks overloads top-to-bottom, uses first match
function process(input: number): 'number';
function process(input: string | number): 'string-or-number';
function process(input: string): 'string'; // Never reached! string matches overload 2
// process('hello') resolves to overload 2, not overload 3
const result = process('hello');
// Type: 'string-or-number' — not 'string'
// Fix — put more specific overloads first
function process(input: number): 'number';
function process(input: string): 'string'; // More specific — before union
function process(input: string | number): 'string-or-number';
function process(input: string | number): string {
if (typeof input === 'number') return 'number';
return 'string';
}Use @ts-expect-error to test which overload is selected:
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
// Test: ensure format(42) returns string (not number)
const result = format(42);
// Hover over 'result' in IDE to see inferred type
// Test that invalid calls are rejected
// @ts-expect-error — number | string should not match any overload directly
format(Math.random() > 0.5 ? '42' : 42);Still Not Working?
any in overloads — using any in an overload signature disables type checking for that overload. TypeScript accepts any argument without error. Replace with proper union types.
Declaration merging — function overloads in .d.ts files work through declaration merging. All signatures must appear consecutively (no other declarations between them).
Constructor overloads — class constructors follow the same overload rules as functions:
class Connection {
constructor(url: string);
constructor(host: string, port: number);
constructor(url: string, port?: number) {
// ...
}
}Async overloads — overloaded async functions must have Promise<T> as the return type in both overloads and the implementation:
async function fetchData(id: number): Promise<User>;
async function fetchData(ids: number[]): Promise<User[]>;
async function fetchData(idOrIds: number | number[]): Promise<User | User[]> {
// ...
}For related TypeScript issues, see Fix: TypeScript Mapped Type Error and Fix: TypeScript Declaration File Error.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: pdf-lib Not Working — PDF Not Generating, Fonts Not Embedding, or Pages Blank
How to fix pdf-lib issues — creating PDFs from scratch, modifying existing PDFs, embedding fonts and images, form filling, merging documents, and browser and Node.js usage.
Fix: Pusher Not Working — Events Not Received, Channel Auth Failing, or Connection Dropping
How to fix Pusher real-time issues — client and server setup, channel types, presence channels, authentication endpoints, event binding, connection management, and React integration.
Fix: date-fns Not Working — Wrong Timezone Output, Invalid Date, or Locale Not Applied
How to fix date-fns issues — timezone handling with date-fns-tz, parseISO vs new Date, locale import and configuration, DST edge cases, v3 ESM migration, and common format pattern mistakes.
Fix: Zod Validation Not Working — safeParse Returns Wrong Error, transform Breaks Type, or discriminatedUnion Fails
How to fix Zod schema validation issues — parse vs safeParse, transform and preprocess, refine for cross-field validation, discriminatedUnion, error formatting, and common schema mistakes.