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.
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.
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.
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.
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.
Practice these patterns on Buglyst
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.
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.
A GraphQL resolver overload that silently broke in production
Timeline
- 09:15Deploy to staging, tests pass
- 10:30Customer reports 'Internal server error' on a specific query
- 10:45Check logs: error references a TypeScript overload resolution failure
- 11:00Reproduce locally with the exact query arguments
- 11:20Isolate the overload: it's a resolver function with 3 overloads
- 11:35Notice the second overload is too broad, intercepting calls meant for the third
- 11:45Reorder overloads and redeploy staging
- 12:00Staging passes, deploy to production, monitor for errors
- 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 failure involved the overload set rather than Apollo's runtime. The array overload appeared after broader signatures, and its return type did not match the caller's expectation. Reordering the signatures from most specific to least specific made the selected return type explicit and reproducible.
The durable lesson is to test each overload at its call site, order narrow signatures before broad ones, and give each overload an explicit return type. The error may surface in a dependent caller even though the mismatch is in the declaration.
Root cause
The overload set placed a broader signature before the array signature and exposed incompatible return types at the call site. TypeScript checks argument count; it does not treat a one-argument overload as a match for a two-argument call. The fix is to order the intended signatures explicitly and align their return types with the caller.
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.
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.
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.
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.
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.
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.