ESC

Type to search the knowledge base.

Function Overloads

Declare multiple call signatures so one implementation returns different types based on arguments — without lying with unions everywhere.

advanced3 min read
  • 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

  1. Implementation must be compatible with every overload.
  2. Often typed wider (string, unknown, unions).
  3. 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.”

Further reading

Related guides