One Function, Multiple Type Signatures
Overloads
Function overloads let one implementation present multiple call signatures to callers. Use sparingly — generics are often better.
What you'll learn
- Declare overload signatures
- Write the implementation signature
- Recognize when generics replace overloads
Overloads let one function present multiple call signatures to callers — useful when the return type depends on which arguments were passed.
The Shape
// overload signatures (the "public" API)
function lookup(id: string): User;
function lookup(id: number): User;
function lookup(ids: string[]): User[];
// implementation signature (must be compatible with all overloads)
function lookup(arg: string | number | string[]): User | User[] {
if (Array.isArray(arg)) return arg.map(id => fetchUser(id));
return fetchUser(String(arg));
} Two parts:
- Overload signatures — public APIs. Multiple declarations, no body.
- Implementation signature — the actual function. Its parameter / return types must be compatible with all overloads but are NOT visible to callers.
How Callers See It
const a = lookup("1"); // User
const b = lookup(2); // User
const c = lookup(["1", "2"]); // User[]
lookup(true);
// ~~~~ No overload matches this call. TS only considers the overload signatures when type-checking calls. The implementation signature is internal.
When Generics Are Better
The example above is much cleaner as a generic:
function lookup<T extends string | number | string[]>(arg: T): T extends string[] ? User[] : User {
// implementation
} But honestly, even that is over-engineered. The simplest fix is
two functions — lookup(id) and lookupMany(ids).
When Overloads Are the Right Tool
- A function genuinely needs many signatures
- You’re typing a JavaScript library whose runtime branches on arguments
- The relationship between input and output types is hard to express with generics
For most application code, overloads are overkill. Two functions or a generic is usually clearer.
Method Overloads
Same idea on a class:
class Selector {
query(id: string): HTMLElement | null;
query(ids: string[]): HTMLElement[];
query(arg: string | string[]): HTMLElement | HTMLElement[] | null {
// implementation
return null as any;
}
} Up Next
Typing callback functions cleanly.
Callback Types →