TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
228: Function Overload Resolution Order
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;
}
There are no comments for now.