Ch. 2 · TypeScript

TypeScript Function Overloads and Resolution

Model multiple call shapes with overload signatures, order them correctly, and know when a union return type is the clearer choice.

~3 min readintermediateupdated Oct 4, 2026

A function sometimes accepts several distinct call shapes and returns a different type for each. TypeScript expresses this with overload signatures — several declarations with no body — followed by one implementation signature. Callers see the overloads; the engine picks the first signature that matches, top to bottom.

Before you start

You should be comfortable with function types, union types and generic constraints. This article is about call-shape modeling; it does not cover ThisParameterType or advanced generic inference.

Step-by-step walkthrough

Step 1: List the specific signatures first

Resolution is ordered, so put the narrow signatures before the broad ones. If (value: string) comes before (value: string | number), a string argument matches the first. Reordering the declarations can change which return type a caller sees even though the implementation never changes.

Step 2: Keep one compatible implementation signature

The implementation signature must be compatible with every overload, but callers can never call it directly. A common bug is making the implementation signature too specific, so it no longer covers all the overloads; the compiler then reports that a signature is not assignable. Widen the implementation parameters and narrow with runtime checks inside the body.

Step 3: Choose overloads or a union return deliberately

Overloads are worth it when the call shapes genuinely differ (different argument counts or literal discriminants). When the only difference is the return type based on an argument’s type, a single generic or conditional return type is usually clearer and avoids resolution-order surprises.

Worked scenario

The two overloads produce different return types for the same function name.

function parse(value: string): number;
function parse(value: number): string;
function parse(value: string | number): number | string {
  return typeof value === 'string' ? value.length : String(value);
}
const length = parse('abc'); // number
const text = parse(12); // string
TypeScript

Walk through the example

parse('abc') matches the first overload and is typed number. parse(12) skips the first signature and matches the second, so it is typed string. The implementation signature is broader than either overload and is invisible at call sites: writing parse(true) is an error even though the implementation’s string | number parameter would not have accepted it anyway.

Common mistake

Writing overloads whose signatures overlap in a surprising order, then wondering why the wrong return type appears. A related mistake is putting application logic in a signature: overloads describe shapes only. Finally, do not reach for overloads when a single signature with a conditional return type (or a literal-typed parameter) would read better and produce the same errors.

Verify the behavior

Assign each call to a typed variable and confirm the inferred types differ (const a: number = parse('x')). Call with a value that matches no overload and check the compiler rejects it. Reorder the overloads and observe that a call matching both now resolves to the first one, which demonstrates that order is part of the API.

Interview exercise

You have get(key: string) and get(keys: string[]). Should these be two overloads or two differently named methods?

Answer and reasoning

Two overloads are reasonable because the argument shapes differ (single value versus list) and the return types differ (Value versus Map<string, Value>). The overloads keep one familiar name while making the list form explicit. If the two behaviors were conceptually different operations, distinct names would be clearer; overloads should express the same operation with different inputs, not unrelated commands.

Follow-up discussion

How many overloads is too many? When the list becomes hard to read, the function is probably doing several jobs; split it. Can overloads be generic? Yes, and a generic overload often replaces several concrete ones, provided the relationship between parameters and the return type can be expressed.

Continue learning

Compare this with conditional types and type versus interface when deciding how to model a callable shape. Read the TypeScript function handbook and take the TypeScript MCQs.

More in TypeScript

read ✓TypeScript · hard

TypeScript Abstract Classes and Contracts

Share behavior with abstract classes, enforce required members, and decide when an interface or composition is the better contract.

~2 min readread →
read ✓TypeScript · mid

TypeScript Enums vs Union Types

Compare enums with unions of string literals: runtime cost, serialization, exhaustiveness and which one fits application code.

~3 min readread →
esc