ProgrammingTypeScript Developer

How to implement and correctly type overloads in TypeScript at the signature level? Why can't you use multiple function bodies, and what happens to parameter types inside the function body? Provide practical usage scenarios and the most common mistakes.

Pass interviews with Hintsage AI assistant

Answer.

Function overloading allows you to create a single function with different variants of accepted parameters and return values. In other languages, like C# or Java, you can declare multiple functions with the same name but with different types or numbers of arguments. TypeScript brings this mechanism closer through signature overloading at the type level, but the function body is always one.

The background of the issue is that JavaScript has no native support for type or parameter count overloading. With the emergence of TypeScript, overloading is emulated through declaring several function signatures consecutively and having a single implementation, within which you manually distinguish the type you are currently working with.

The problem arises if the compliance with declared signatures is not monitored: inside the function body, TypeScript assigns the most general type (the union of all parameter types) to the parameters, and the programmer has to perform checks and type guards.

Solution: Strictly type overloads in explicit signatures, work correctly with union types and type guards, and properly document behavior.

Example code:

function format(value: string): string; function format(value: number, locale: string): string; function format(value: any, locale?: string): string { if (typeof value === 'number') { return value.toLocaleString(locale); } return value.trim(); } format(' hello '); // 'hello' format(123456, 'ru-RU'); // '123 456'

Key features:

  • Several overloaded signatures in a row, one implementation beneath them.
  • For working with parameters in the body, either a union type or any/unknown is used.
  • The type of the implementation body is broader/universal than the signatures above it.

Tricky questions.

Can you declare multiple functions with the same name and different bodies, like in C#?

No. In TypeScript (and JavaScript), there is effectively only one function with that name. Overloads work only at the type level for the compiler, but there is only one function body.

What will happen if you don't implement a signature with the most general parameters after the overloaded signatures?

TypeScript will throw a compilation error. There should always be one implementation function whose parameters cover all possible overload variants.

function foo(a: string): string; function foo(a: number): number; // no body — error

Can I use properties specific to one specific signature inside the body?

No, only after a type check (type guard) or type assertion. Otherwise, TypeScript will not allow you to use these properties directly, as it is not known which signature the call is currently following.

function bar(x: string): number; function bar(x: number): number; function bar(x: string | number): number { if (typeof x === 'string') return x.length; return x * 2; }

Common mistakes and anti-patterns

  • They do not add the implementation after overloaded signatures — compilation will fail.
  • They use specific properties in the function body without type checking.
  • They complicate overloads too much, leading to unreadable code.
  • They do not explicitly describe return types or forget to update signatures when the function logic changes.

Example from life

Negative case

Forgot to add the universal signature-implementation:

function sum(a: string, b: string): string; function sum(a: number, b: number): number; // no implementation — the compiler complains

Pros:

  • The idea of describing a convenient API

Cons:

  • The code does not compile, and it is impossible to handle any variant

Positive case

Implementing overloading according to the standard:

function sum(a: string, b: string): string; function sum(a: number, b: number): number; function sum(a: any, b: any): any { return typeof a === 'string' && typeof b === 'string' ? a + b : a + b; }

Pros:

  • All variants are handled correctly, types are inferred at compile time

Cons:

  • The function body requires manual type control, one should not forget to check each variant