Skip to Content
Course content

228: Function Overload Resolution Order

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Why does the order of my overloads even matter?

In TypeScript, function overloads aren't like method overloading in Java or C#. In those languages, the runtime looks at the actual types and picks the "best" match. TypeScript is different. It's much simpler—and potentially more frustrating if you don't know the secret: TypeScript picks the first overload that matches the arguments you passed.

It doesn't keep searching to see if there's a "better" or "more specific" match further down the list. Once it finds a signature that works, it stops right there and uses that return type. If you put your most general signature at the top, your specific ones become dead code.

What happens if I put a generic signature before a specific one?

This is the most common way I see developers shoot themselves in the foot. Let's say you're building a search function for a user database. You want to allow searching by a specific ID (which returns one user) or by a search term (which returns an array of users).

// ❌ The Wrong Way
function search(id: string | number): User[]; // Generic catch-all
function search(id: string): User;           // Specific case

const result = search("123"); 
// result is User[] because the first overload matched!

In the example above, "123" is a string, which satisfies string | number. TypeScript says, "Yep, that works!" and stops looking. The second overload—the one you actually wanted for strings—is completely ignored. To fix this, you have to move the most restrictive types to the top.

// ✅ The Right Way
function search(id: string): User;           // Specific first
function search(id: string | number): User[]; // General second

const result = search("123"); 
// result is now User, just as intended.

How do I handle overlapping types without breaking resolution?

When your types overlap, you have to think of your overloads as a filter. You want the "finest" mesh at the top and the "widest" mesh at the bottom. I usually recommend listing your most unique or constrained types first, then your unions, and finally any "catch-all" signatures.

Here is a real-world pattern I use when handling configuration objects versus simple strings:

interface Config { timeout: number; retry: boolean; }

function connect(options: Config): void; // Most specific
function connect(url: string): void;    // Less specific
function connect(input: string | Config): void { 
    // Implementation handles both
    if (typeof input === 'string') {
        console.log(`Connecting to ${input}...`);
    } else {
        console.log(`Connecting with timeout ${input.timeout}...`);
    }
}

If you swapped the Config and string overloads here, it wouldn't actually break because they don't overlap. But if you had an overload for any or unknown, putting that first would effectively disable every other overload you wrote. It's a silent failure, which is why it's so easy to miss until you notice your IDE is giving you the wrong return type.




📋 Practical Task

Fixing the Shadowed DateFormatter

You are working on a legacy utility library. There is a formatDate function that is supposed to return a string when passed a Date object, but it should return null if it's passed a string (because the function only formats actual Date objects, it doesn't parse strings).

However, the current implementation has the overloads in the wrong order, and TypeScript is treating every call as if it returns string | null. Fix the resolution order so that passing a Date object correctly results in a string type.

interface DateFormatter {
    formatDate(input: string | Date): string | null;
    formatDate(input: Date): string;
}

// Your task: Rewrite the overloads for the function below 
// so that the specific Date overload is resolved first.

function formatDate(input: any): string | null {
    if (input instanceof Date) {
        return input.toLocaleDateString();
    }
    return null;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.