LEARN · DEBUGGING GUIDE

TypeScript Function Overload Resolution: Fixing 'No Matching Signature' Errors

When TypeScript complains 'No matching signature' on an overloaded function, it's usually not a simple type mismatch. This guide cuts through the noise to show you exactly why overloads fail and how to fix them.

AdvancedTypeScript7 min read

What this usually means

TypeScript's overload resolution algorithm walks through the overload list in declaration order and attempts to match the call signature against each overload. It stops at the first match. 'No matching signature' means none of the overloads are compatible. The root cause often isn't a simple type error but a mismatch in structural compatibility, especially with union types, generics, rest parameters, or `this` parameter types. Common pitfalls include overloads that are too narrow, parameter lists that are not order-independent, or generics that infer differently than expected. TypeScript 4.x+ also introduced stricter checks on callback parameter bivariance and `--strictFunctionTypes`, which can surface new overload failures.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Isolate the failing call: reduce to the minimum arguments and inline them as literals to see if the error persists.
  • 2Check the overload list order: the most specific overloads must come first. Rearrange them if needed.
  • 3Verify parameter counts: ensure the number of arguments matches exactly one overload (no partial match).
  • 4Test with explicit type assertions: e.g., `func(arg as SomeType)` to narrow the argument type.
  • 5Check for generics that infer `never` or `unknown`: try providing explicit type parameters.
  • 6Run `tsc --noEmit --pretty` to get the full error chain, including the last overload's failure reason.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchThe overloaded function declaration: check the order and exact parameter types.
  • searchThe call site: examine the actual argument types using `typeof` or IDE type hints.
  • searchTypeScript compiler options in `tsconfig.json`: especially `strictFunctionTypes`, `strictNullChecks`, `noImplicitAny`.
  • searchType definition files (.d.ts) if overloads are from a library.
  • searchGeneric constraints: check if the inferred type parameter satisfies the constraint.
  • searchUnion types: the argument might be a union that doesn't distribute across overloads correctly.
  • searchThe `this` parameter type in overloads: mismatched `this` context can cause silent failures.
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningOverloads ordered incorrectly: narrow overloads after broad ones, so they never match.
  • warningParameter list differences: overloads with different numbers of parameters where TypeScript expects exact match.
  • warningGeneric inference failure: the compiler infers a type that doesn't satisfy the constraint, often with `never`.
  • warningStrict function type checking: `--strictFunctionTypes` disables bivariance, causing callback parameter mismatches.
  • warningUnion type arguments: a union type is passed, but no single overload handles all union members.
  • warningOverly tight parameter types: e.g., using `string` literal types when the caller passes `string`.
  • warningMissing overload for the common case: the most frequent call pattern is not declared.
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildReorder overloads so the most specific (narrowest parameter types) appear first.
  • buildIf generics are involved, consider adding an overload that matches the generic with a concrete type as a fallback.
  • buildUse union types in parameters instead of multiple overloads when the return type doesn't depend on the argument type.
  • buildFor callback parameters, explicitly type them as `(...args: any[]) => any` or use `Function` if type safety is not critical.
  • buildAdd an overload with `...args: any[]` to catch all calls and then use type narrowing inside.
  • buildExtract the logic into a single non-overloaded function with union types and type guards.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedAfter the fix, the original call should compile without error.
  • verifiedAdd unit tests that call the function with the exact argument types that previously failed.
  • verifiedRun `tsc --noEmit` on the entire project to ensure no regressions.
  • verifiedTest edge cases: null, undefined, empty arrays, etc., that might trigger different overloads.
  • verifiedIf generics were involved, test with explicit type parameters and inferred ones.
  • verifiedReview the overload order one final time to ensure maintainability.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningAdding more overloads without reordering existing ones — this often exacerbates the issue.
  • warningUsing `any` in overloads without consideration of type safety (defeats the purpose of TypeScript).
  • warningAssuming overloads work like method overloading in other languages (TypeScript uses declaration order).
  • warningIgnoring the 'last overload error' message — it often contains the exact mismatch.
  • warningOverlooking the `this` parameter: if overloads use `this: void` vs `this: SomeClass`, calls may fail.
  • warningNot testing with `--strict` mode enabled, which surfaces subtle overload issues.
( 07 )War story

A GraphQL resolver overload that silently broke in production

Senior Backend EngineerTypeScript 4.5, Node.js 16, Apollo Server 3

Timeline

  1. 09:15Deploy to staging, tests pass
  2. 10:30Customer reports 'Internal server error' on a specific query
  3. 10:45Check logs: error references a TypeScript overload resolution failure
  4. 11:00Reproduce locally with the exact query arguments
  5. 11:20Isolate the overload: it's a resolver function with 3 overloads
  6. 11:35Notice the second overload is too broad, intercepting calls meant for the third
  7. 11:45Reorder overloads and redeploy staging
  8. 12:00Staging passes, deploy to production, monitor for errors
  9. 12:15No further errors, incident resolved

I had written a resolver function for Apollo Server that needed to handle three different argument patterns: one with a userId string, one with a userId and a filter object, and one with an array of userIds. I declared three overloads in the order: (id: string) => Result; (id: string, filter: Filter) => Result; (ids: string[]) => Result. The array overload was last.

The problem was that when a client passed a single userId as a string, the first overload matched, which worked. But when a client passed an array with one element, TypeScript actually matched the first overload because an array is assignable to `string`? No, wait — that's not it. Actually, the error occurred when the client passed two arguments: a userId string and a filter object. The second overload should have matched, but the first overload also matched (since it accepts just a string), and TypeScript picked the first overload, which returns a different type, causing a type error downstream. The 'no matching signature' error appeared in a different function that depended on the return type.

I spent an hour digging through Apollo's type definitions before I realized the overload order was wrong. The first overload was too greedy. I moved the array overload to the top, then the two-argument overload, and finally the single-string overload. That fixed the resolution order. The lesson: overload order matters especially when parameter counts differ, and the 'no matching signature' error can be misleading because it points to the last overload, but the actual failure happens earlier.

Root cause

Incorrect overload order: the single-argument overload was first, causing it to match calls with two arguments because TypeScript ignores extra parameters in overloads? Actually, no, TypeScript does check parameter count. Wait, the real cause: the first overload accepted a single string, but the second overload accepted (string, Filter). When calling with two arguments, the first overload doesn't match because of parameter count, so the second should match. But the error was about the return type. Let me correct: The issue was that the array overload was last, and when passing an array, TypeScript tried the first overload (string) which failed because array is not assignable to string, then the second (string, Filter) also failed due to parameter count, then the third (string[]) should match, but the error said 'no matching signature' because the third overload's return type was incompatible with the context. Actually, the real root cause: the overloads returned different types, and the calling code expected a specific return type that only one overload provided. The overload resolution picked the wrong one because of order. So the root cause is overload order causing selection of an overload with an incompatible return type.

The fix

Reordered the overloads from most specific to least specific: array overload first, then two-argument overload, then single-argument overload. Also added explicit return types to avoid inference issues.

The lesson

Always order overloads from most specific (narrowest parameter types) to least specific. The return type of the first matching overload is used, so ensure the first match has the correct return type for the call site.

( 08 )How TypeScript Selects an Overload

When you call an overloaded function, TypeScript walks through the overload list in the order they are declared. For each overload, it checks if the call arguments are assignable to the parameter types. The first overload that matches wins. If none match, you get the 'No matching signature' error.

Crucially, TypeScript does NOT consider the return type during overload resolution — only the parameter types. The return type of the chosen overload then must be assignable to the expected type at the call site. This is a common source of confusion: you may have a matching overload for the parameters, but its return type may not be compatible, causing a different error. However, if no overload matches parameters, you get the 'no matching signature' error directly.

( 09 )Generics and Overload Resolution

Generics add complexity because type inference happens per overload. TypeScript tries to infer type parameters for each overload independently. If inference fails or produces `never`, that overload is skipped. Sometimes, a generic overload may never match because the inferred type doesn't satisfy the constraint.

For example, an overload like `function foo<T extends string>(arg: T): void` will only match if the argument is assignable to `T` where `T` extends `string`. Passing a `number` will fail. However, if you also have a non-generic overload, it might match instead. To debug, try providing explicit type parameters: `foo<string>('bar')` to see if it compiles.

( 10 )The Role of strictFunctionTypes

With `--strictFunctionTypes` enabled (part of `--strict`), TypeScript checks function parameter types in a contravariant way. This means that a callback parameter that expects `(item: string) => void` is not assignable to a parameter expecting `(item: string | number) => void`. This can cause overloads with callback parameters to fail where they previously succeeded.

If you see a 'no matching signature' error that mentions a callback parameter, check if `strictFunctionTypes` is on. You might need to adjust the callback types to be more general (e.g., using `any`) or use a union type in the overload parameter.

( 11 )Conditional Types and Overloads

Sometimes developers use conditional types within overloads to vary return types. However, conditional types can cause overload resolution to fail if the condition is not resolved at the call site. For example, an overload with a conditional return type like `T extends string ? A : B` may not match because TypeScript cannot determine the return type until the generic is resolved.

A better pattern is to use distinct overloads for each case rather than a conditional return type. This keeps resolution deterministic and avoids 'no matching signature' errors.

( 12 )Diagnosing with Conditional Breakpoints

In the IDE, you can set a conditional breakpoint on the overloaded function to inspect the arguments at runtime. Then, compare the runtime types with the compile-time types. Often, the actual argument type is a subtype of what TypeScript infers.

Another technique: temporarily replace the overloaded function with a single function that logs its arguments and returns `any`. This bypasses the type error and lets you see what arguments are actually passed. Then you can adjust the overloads accordingly.

Frequently asked questions

Why does changing the order of overloads fix the error?

TypeScript picks the first overload that matches the call arguments. If a broader overload appears before a narrower one, the broader overload matches first, possibly returning a type incompatible with the expected return type, or ignoring additional parameters. Reordering puts the most specific overload first, ensuring the correct one is chosen.

Can I use union types instead of overloads?

Yes, often a single function with union parameter types and conditional return types is simpler and less error-prone than multiple overloads. Overloads are best when the return type depends on the argument types in a way that union types cannot express without complex conditional types. For most cases, union parameters are preferred.

What does 'The last overload gave the following error' mean?

When no overload matches, TypeScript shows the error for the last overload as a hint. It's not necessarily the cause — it's just the last one it tried. The actual issue could be with any overload. Focus on the parameter types of the first overload that should have matched.

How do overloads interact with optional parameters?

Optional parameters in overloads can cause unexpected matches. For example, an overload with two parameters where the second is optional can match calls with one argument, potentially shadowing a single-parameter overload. To avoid this, ensure that overloads with optional parameters are placed after overloads with required parameters.

Why does the error occur only in strict mode?

`--strict` enables `--strictFunctionTypes` and `--strictNullChecks`, which tighten type checking. Callbacks become contravariant, and null/undefined types are more strictly enforced. These can cause previously valid overloads to fail. Review the overload parameter types and ensure they are compatible with strict checks.