Function Overloads
Declare multiple call signatures so one implementation returns different types based on arguments — without lying with unions everywhere.
- typescript
- function-overloads
Sometimes one function has several legal call shapes with different return types. A single signature with wide unions loses precision. Overload signatures list the allowed calls; one implementation body satisfies them all.
Docs: More on Functions — Overloads.
Basic shape
function createElement(tag: 'a'): HTMLAnchorElement;
function createElement(tag: 'canvas'): HTMLCanvasElement;
function createElement(tag: string): HTMLElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
const a = createElement('a'); // HTMLAnchorElement
const el = createElement('section'); // HTMLElement
Callers only see the overload list. The implementation signature is not part of the public API (and is often wider).
When overloads beat unions
// Precise
function parse(input: string): User;
function parse(input: string, opts: { raw: true }): unknown;
function parse(input: string, opts?: { raw?: boolean }): User | unknown {
const data: unknown = JSON.parse(input);
if (opts?.raw) return data;
return parseUser(data);
}
Without overloads, return type stays User | unknown even when raw isn’t passed — annoying for callers.
DOM / event patterns (classic)
function on(
el: Element,
event: 'click',
handler: (ev: MouseEvent) => void,
): void;
function on(
el: Element,
event: 'keydown',
handler: (ev: KeyboardEvent) => void,
): void;
function on(
el: Element,
event: string,
handler: (ev: Event) => void,
): void {
el.addEventListener(event, handler as EventListener);
}
This is how lib.dom typings feel precise for 'click' vs generic events.
Prefer generics when the relationship is uniform
function first<T>(items: T[]): T | undefined {
return items[0];
}
No overload needed — one type parameter covers all cases. Overloads shine when argument patterns diverge, not when only T changes.
Implementation signature rules
- Implementation must be compatible with every overload.
- Often typed wider (
string,unknown, unions). - Runtime still needs branching — types don’t execute.
function format(value: Date): string;
function format(value: number): string;
function format(value: Date | number): string {
if (value instanceof Date) return value.toISOString();
return value.toFixed(2);
}
Order matters
TypeScript picks the first matching overload.
function pick(x: any): any; // never put this first
function pick(x: string): string;
function pick(x: number): number;
Put the most specific signatures first, catch-all last.
Overloads vs conditional return types
type Result<T extends boolean> = T extends true ? string : number;
function f<T extends boolean>(cond: T): Result<T> {
return (cond ? 'yes' : 1) as Result<T>;
}
Conditional types + generics can replace some overloads. Overloads remain more readable for heterogeneous argument lists (different arity, different option objects).
React / library author note
Public hooks sometimes overload:
function useMediaQuery(query: string): boolean;
function useMediaQuery(query: string, ssrDefault: boolean): boolean;
// …
For app code, two named functions (createUser / createUserRaw) often beat clever overloads.
Footguns
| Issue | Mitigation |
|---|---|
| Implementation not assignable | Widen impl params/return |
| Wrong overload order | Specific → general |
| Too many overloads | Split functions or use options object |
Overloads hiding any |
Keep impl honest; avoid any if possible |
// Hard to maintain
function h(a: A): X;
function h(a: A, b: B): Y;
function h(a: A, b: B, c: C): Z;
// consider h(options: Options): Result
Interview out-loud answer
“Overloads declare multiple call signatures with one implementation so return types stay precise per call shape. I put specific signatures first. Prefer generics for uniform maps; prefer options objects if overload lists explode. Implementation signature is wider and not exposed to callers.”
Related on this site
- Generics basics
- Conditional types intro
- Typing React events
- Union and intersection types
- Type assertions safely
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.