Function Overloading in TypeScript: A 2025 Guide with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

TypeScript Function Overloading: Why It's Not "Magic," But a Tool for Smart Code

\n\n

If you've ever written a JavaScript function that accepts "a string, a number, or even an array," you know that unpleasant moment: inside the function is a mess of if statements, and outside, there's complete uncertainty for whoever calls that function. TypeScript solves this problem elegantly, but not always obviously. Function overloading is not about creating multiple functions with the same name (like in Java or C++). It's about declaring multiple signatures for a single implementation.

\n\n

In 2025, when TypeScript has become the de facto standard for serious frontend and backend projects, the ability to skillfully configure overloading is not a "gimmick," but a skill that makes your code readable, type-safe, and convenient for your team. Let's figure out how it works, where beginners get tripped up, and which patterns you should adopt.

\n\n

What is Function Overloading Really?

\n\n

Imagine you are writing a function formatDate. It should be able to:

\n
    \n
  • Accept a Date object and return a string.
  • \n
  • Accept a string in ISO format and return a string.
  • \n
  • Accept a number (timestamp) and return a string.
  • \n
\n\n

If you simply write function formatDate(input: Date | string | number): string, TypeScript won't be able to tell you which specific type you are passing in a particular call. The IDE will show a general type, and you'll lose autocompletion. Overloading solves this:

\n\n
// Overload signatures\nfunction formatDate(input: Date): string;\nfunction formatDate(input: string): string;\nfunction formatDate(input: number): string;\n\n// Implementation signature\nfunction formatDate(input: Date | string | number): string {\n    if (input instanceof Date) {\n        return input.toISOString();\n    } else if (typeof input === 'string') {\n        return new Date(input).toISOString();\n    } else {\n        return new Date(input).toISOString();\n    }\n}
\n\n

Now, when you call formatDate(new Date()), TypeScript knows that a string is returned and provides type hints. There's no magic — there's strict typing.

\n\n

Rule #1: Overload Signatures Cannot Be Implementations

\n\n

The most common mistake beginners make is trying to write the function body in each signature. Don't do this:

\n\n
// ❌ Error: duplicate implementation\nfunction foo(x: number): number {\n    return x * 2;\n}\nfunction foo(x: string): string {\n    return x.toUpperCase();\n}
\n\n

TypeScript does not support multiple implementations with the same name. Always one implementation, multiple declarations.

\n\n

Rule #2: The Order of Signatures Matters

\n\n

TypeScript checks signatures from top to bottom. The first matching signature determines the return type. Therefore:

\n
    \n
  • Place more specific types higher (e.g., string before any).
  • \n
  • Avoid overlapping signatures that could confuse the compiler.
  • \n
\n\n
// Good: specific case first\nfunction createButton(label: string, disabled?: boolean): HTMLButtonElement;\nfunction createButton(options: { label: string; disabled?: boolean }): HTMLButtonElement;\nfunction createButton(labelOrOptions: string | { label: string; disabled?: boolean }, disabled?: boolean): HTMLButtonElement {\n    // implementation\n}
\n\n

Rule #3: Use Union Types if You Have More Than 3 Signatures

\n\n

Overloading is a powerful tool, but don't overuse it. If you have 5+ signatures, the code becomes unreadable. In 2025, a good practice is considered:

\n
    \n
  • Up to 3 signatures — use overloading.
  • \n
  • More than 3 — consider a Union Type or a

Blogs

Book Recommendations