TypeScript Function Overloading

A reference guide to declaring and implementing TypeScript polymorphic function and method overloads.

TL;DR

  1. Declare multiple function overload signatures before the single implementation.
  2. Write compatible implementation parameters using general any or union types.
  3. Return distinct types like number or string based on inputs.

Basic Overload Syntax

    overload signatures

    Declares public callable contracts above the implementation.

    function toDate(ts: number): Date;
    function toDate(m: number, d: number, y: number): Date;
    function toDate(
      m: number, d?: number, y?: number
    ): Date {
      if (d !== undefined && y !== undefined) {
        return new Date(y, m, d);
      }
      return new Date(m);
    }
    calling overloads

    Invokes the function matching declared parameter arity.

    const d1 = toDate(12345678);
    const d2 = toDate(5, 15, 2026);
    // toDate(1, 2); // Error: No overload matches
    hidden implementation

    Callers cannot invoke the implementation signature directly.

    // Implementation signature is not callable
    // if it differs from declared overloads

Return Type Branching

    type-dependent returns

    Returns distinct types based on the input argument type.

    function parse(x: string): number;
    function parse(x: number): string;
    function parse(x: string | number): string | number {
      return typeof x === 'string' ? Number(x) : String(x);
    }
    const num = parse('42'); // number
    boolean flag dispatch

    Returns single items or collections based on boolean flags.

    function find(all: true): string[];
    function find(all: false): string;
    function find(all: boolean): string | string[] {
      return all ? ['a', 'b'] : 'a';
    }
    exact literal matching

    Dispatches return shapes based on string literal tags.

    function getElement(tag: 'input'): HTMLInputElement;
    function getElement(tag: 'div'): HTMLDivElement;
    function getElement(tag: string): HTMLElement {
      return document.createElement(tag);
    }

Parameter Arity Overloads

    varying parameter count

    Allows functions to accept different counts of arguments.

    function log(msg: string): void;
    function log(msg: string, code: number): void;
    function log(msg: string, code?: number): void {
      console.log(code ? `[${code}]: ${msg}` : msg);
    }
    callback vs promise

    Supports both callback-based and Promise-based flows.

    function load(cb: (data: string) => void): void;
    function load(): Promise<string>;
    function load(cb?: Function): any {
      if (cb) return cb('data');
      return Promise.resolve('data');
    }
    options object overload

    Accepts either shorthand parameters or a configuration map.

    interface OpenOpts { url: string; modal: boolean; }
    function open(url: string): void;
    function open(opts: OpenOpts): void;
    function open(arg: any): void {}

Class Method Overloading

    method overloads

    Declares overloaded signatures inside class bodies.

    class StorageService {
      get(key: string): string | null;
      get(keys: string[]): Record<string, string>;
      get(k: string | string[]): any {
        return Array.isArray(k) ? {} : null;
      }
    }
    constructor overloading

    Supports multiple constructor instantiation shapes.

    class Point {
      x: number; y: number;
      constructor(coords: [number, number]);
      constructor(x: number, y: number);
      constructor(a: any, b?: any) {
        this.x = Array.isArray(a) ? a[0] : a;
        this.y = Array.isArray(a) ? a[1] : b;
      }
    }
    static method overload

    Applies overload declarations to static utility methods.

    class Parser {
      static parse(raw: string): object;
      static parse(raw: string[]): object[];
      static parse(arg: any): any {}
    }

Overloaded Interfaces

    callable interface

    Expresses multiple call signatures on a single contract.

    interface Greeter {
      (name: string): string;
      (name: string, count: number): string[];
    }
    const greet: Greeter = ((n: string, c?: number) => {
      return c ? Array(c).fill(n) : n;
    }) as Greeter;
    event listener interface

    Binds event payload types to specific event names.

    interface Emitter {
      on(e: 'click', cb: (x: number) => void): void;
      on(e: 'error', cb: (err: Error) => void): void;
    }
    method in interface

    Declares overloaded method contracts on interfaces.

    interface Repository {
      find(id: string): object;
      find(filter: object): object[];
    }

Tips

  1. Order overload signatures from most specific to most general because TypeScript resolves function calls from top to bottom.
  2. Prefer union parameters like (x: string | number) whenever return types do not change based on parameter inputs.

Warnings

  1. Remember that the implementation signature is invisible to callers and cannot be invoked with any parameters directly.
  2. Overload signatures are compile-time only, requiring typeof checks inside the implementation body to prevent runtime exceptions.

In Practice

FAQ