TypeScript Overloads — Complete Guide
In this tutorial, you will learn about TypeScript Overloads. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript overloads let you define multiple call signatures for a single function, where each signature specifies different parameter types and return types so that inputs determine outputs at the type level with precise inference.
What You'll Learn
- Function overload signatures vs implementation
- Constructor overloads
- Call signatures in object types
- When to use overloads vs union types
Why It Matters
Overloads make APIs ergonomic by providing one function that handles multiple calling patterns with precise return types. Without overloads, you would need separate functions for each variant, losing the conceptual unity of the operation.
Real-World Use
The Doda Browser browser.tabs.query() has multiple overloads: query(queryInfo: object): Promise<Tab[]> and query(tabId: number): Promise<Tab>. The correct return type (array vs single) is inferred based on whether you pass an object or a number.
Learning Path
flowchart LR A[Variance] --> B[Overloads] B --> C[Branded Types] B --> D[You Are Here] C --> E[tsconfig Deep Dive] E --> F[Project References]
Function Overloads
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean;
function process(value: string | number | boolean): string | number | boolean {
if (typeof value === "string") return value.toUpperCase();
else if (typeof value === "number") return value * 10;
return !value;
}
const str = process("hello"); // type: string
const num = process(42); // type: number
const bool = process(true); // type: boolean
console.log(str); // HELLO
console.log(num); // 420
console.log(bool); // false
Think of overloads as a menu — each signature guarantees specific inputs and outputs. The implementation is the kitchen that handles all options.
Why Not Just Union Types?
Without overloads, a union parameter type produces a union return type — too wide. Overloads preserve the input-output relationship.
Real-World Overload Example
interface User { id: string; name: string; email: string; }
function getUsers(): Promise<User[]>;
function getUsers(id: string): Promise<User>;
function getUsers(ids: string[]): Promise<User[]>;
function getUsers(idOrIds?: string | string[]): Promise<User | User[]> {
if (idOrIds === undefined) return fetch("/api/users").then(r => r.json());
else if (Array.isArray(idOrIds)) {
return Promise.all(idOrIds.map(id => fetch(`/api/users/${id}`).then(r => r.json())));
}
return fetch(`/api/users/${idOrIds}`).then(r => r.json());
}
const all = await getUsers(); // type: User[]
const one = await getUsers("123"); // type: User
const many = await getUsers(["1", "2"]); // type: User[]
Constructor Overloads
class Vector2D {
private x: number;
private y: number;
constructor();
constructor(x: number, y: number);
constructor(obj: { x: number; y: number });
constructor(xOrObj?: number | { x: number; y: number }, y?: number) {
if (typeof xOrObj === "undefined") { this.x = 0; this.y = 0; }
else if (typeof xOrObj === "object") { this.x = xOrObj.x; this.y = xOrObj.y; }
else { this.x = xOrObj; this.y = y ?? 0; }
}
toString(): string { return `(${this.x}, ${this.y})`; }
}
const v1 = new Vector2D(); // (0, 0)
const v2 = new Vector2D(3, 4); // (3, 4)
const v3 = new Vector2D({ x: 1, y: 2 }); // (1, 2)
Call Signatures in Object Types
type ValidationFunction = {
(value: string): boolean;
errorMessage: string;
name: string;
};
function createMinLengthValidator(minLength: number): ValidationFunction {
const validator = ((value: string): boolean => value.length >= minLength) as ValidationFunction;
validator.errorMessage = `Must be at least ${minLength} characters`;
validator.name = `minLength(${minLength})`;
return validator;
}
const min5 = createMinLengthValidator(5);
console.log(min5("hello")); // true
console.log(min5("hi")); // false
console.log(min5.errorMessage);
Common Mistakes
1. Implementation Signature Being Callable
The implementation is not part of the public API. Only overload signatures are callable.
2. Incompatible Implementation Signature
The implementation must handle all overload cases with a type that covers all signature types.
3. Not Ordering Overloads from Most Specific to Least
TypeScript matches overloads in order. Put more specific signatures first.
4. Using Overloads When Union Types Simpler
function identity<T>(x: T): T { return x; } // Simpler than overloads
5. Forgetting Overloads Are Compile-Time Only
Overload signatures don't affect runtime. The implementation handles all logic.
Practice Questions
What is the difference between overload signatures and implementation? Overload signatures define the public API; the implementation handles all variants but is not directly callable.
Can constructor overloads change the instance type? No. All overloads produce the same class instance type.
When to use a call signature in an object type? When a function also has properties (like jQuery's
$being callable with.ajaxSettings).What happens with no matching overload? TypeScript reports "No overload matches this call."
Challenge: Write overloads for formatDate accepting (Date), (number), and (year, month, day).
FAQ
Mini Project: Parser with Overloads
type ParseResult =
| { type: "number"; value: number }
| { type: "string"; value: string }
| { type: "boolean"; value: boolean }
| { type: "array"; value: ParseResult[] };
function parse(input: string): ParseResult;
function parse(input: number): ParseResult;
function parse(input: boolean): ParseResult;
function parse(input: string | number | boolean): ParseResult {
if (typeof input === "string") {
return { type: "string", value: input };
} else if (typeof input === "number") {
return { type: "number", value: input };
}
return { type: "boolean", value: input };
}
console.log(parse("hello")); // { type: "string", value: "hello" }
console.log(parse(42)); // { type: "number", value: 42 }
console.log(parse(true)); // { type: "boolean", value: true }
What's Next
Now explore branded types for nominal typing:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/28-variance" >}} | Review variance |
| {{< ref "/programming-languages/typescript/30-branded-types" >}} | Branded types, nominal simulation |
| {{< ref "/programming-languages/typescript/31-tsconfig-deep-dive" >}} | tsconfig options deep dive |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro