Question Details

No question body available.

Tags

typescript

Answers (1)

Accepted Answer Available
Accepted Answer
March 11, 2026 Score: 2 Rep: 340,321 Quality: High Completeness: 80%

There are quite a few design limitations with overloaded functions in TypeScript. They work well enough when you call them directly with arguments of specific types. Once you start trying to do more with them they tend to be brittle.


The first major issue is that when you need to infer from an overloaded function type, TypeScript will choose one call signature without trying to find the "correct" one. This is usually the last call signature. See microsoft/TypeScript#43187. The map() array method is generic in the return type of the callback. So when faced with [1, 2, 3].map(f), TypeScript has to infer a generic type argument from the type of f. It consults the last call signature, sees that the return type is effectively number[], and that's what it infers:

const wha = [1,2,3].map(f);
// const wha: number[][] = [1,2,3].map(f);

This is a well-known limitation with overloads, and people have run into this with map() before, as in TypeScript overloaded generic function's return type inferred incorrectly when used with Array.prototype.map.


But wait, that means TypeScript is treating f as being of a type like (x: number) => number[]. Sure enough, you are allowed to assign f to that type:

const g: (x: number) => number[] = f; // no error

But that assignment really shouldn't be allowed. Neither call signature supports (x: number) => number[]. Which brings us to another design limitation with overloads as it relates to generic functions. When comparing two single-call-signature function types, TypeScript will try to resolve generic type arguments properly. As soon as one of the function types is overloaded, it gives up and just "erases" the generics and allows all kinds of inappropriate assignments. So instead of seeing f as being ((x: T) => T) & ((x: T[]) => T[]), TypeScript sees it as something like ((x: any) => any) & ((x: any[]) => any[]), and unfortunately that is compatible with (x: number) => number[]. See microsoft/TypeScript#50050.

This is a known limitation with overloads but it doesn't seem to be well-documented. The correct behavior would be to perform the proper generic resolution for each pair of call signatures in the two function types. But this scales quadratically in the number of overloads (i.e., when comparing a function with m overloads to one with n overloads, you have to perform m×n comparisons) and so it is not done for performance reasons.


So that's what's going on. TypeScript is making two simplifications when dealing with overloads, and resulting in incorrect type system behavior. Personally I recommend avoiding overloads unless you really need them. Presumably the example here is just an example, so suggesting "try to use a single call signature like (x: T) => T instead" or "use two different functions like fNum and fArr if you have two different behaviors" might not apply directly to an actual use case, but those are the general ideas.

Playground link to code