================================================================================ TYPESCRIPT FUNDAMENTALS - CHALLENGE 1 SOLUTION Chapter 8: Functions & Overloads Challenge: Basic Function Overload ================================================================================ PROBLEM: Create a function that accepts either a string or a number and returns a boolean. If string, check if length > 5. If number, check if > 100. SOLUTION: ================================================================================ // Define the overload signatures function isLarge(value: string): boolean; function isLarge(value: number): boolean; // Implementation that handles both cases function isLarge(value: string | number): boolean { if (typeof value === "string") { // For strings, check if length > 5 return value.length > 5; } else { // For numbers, check if > 100 return value > 100; } } // Testing the solution console.log(isLarge("hello")); // false (length = 5) console.log(isLarge("helloworld")); // true (length = 10) console.log(isLarge(50)); // false (50 is not > 100) console.log(isLarge(150)); // true (150 > 100) console.log(isLarge("a")); // false (length = 1) console.log(isLarge(999)); // true (999 > 100) ================================================================================ WHY THIS WORKS: ================================================================================ 1. Overload Signatures - First line: function isLarge(value: string): boolean; - Second line: function isLarge(value: number): boolean; - These tell TypeScript the possible ways to call the function - No body - just the signature 2. Implementation - Line with body: function isLarge(value: string | number): boolean - Uses union type to accept both string and number - This matches both overload signatures 3. Type Narrowing - Inside the function, we check typeof to narrow the type - When type === "string", we know it's a string and use .length - When type !== "string", we know it's a number and use arithmetic 4. Caller Benefits - When you call isLarge("text"), TypeScript knows it returns boolean - When you call isLarge(42), TypeScript also knows it returns boolean - Different behavior, but same return type - no need for separate overloads ================================================================================ ALTERNATIVE APPROACHES: ================================================================================ Approach 1: Without overloads (simpler union) function isLarge(value: string | number): boolean { if (typeof value === "string") { return value.length > 5; } else { return value > 100; } } // This works fine when return type is always the same // Overloads are useful when return types differ Approach 2: More descriptive overload names function isLargeString(value: string): boolean { return value.length > 5; } function isLargeNumber(value: number): boolean { return value > 100; } // Separate functions are sometimes clearer than overloads Approach 3: Using generics with conditional types function isLarge(value: T): boolean { if (typeof value === "string") { return (value as string).length > 5; } else { return (value as number) > 100; } } // More complex but can be useful in larger systems ================================================================================ KEY CONCEPTS TO UNDERSTAND: ================================================================================ 1. Why Overloads Matter Here Even though the return type is always boolean, the behavior is different based on the input type. The overload signatures make this explicit to callers. 2. Type Guard Pattern Inside the implementation, typeof is a type guard that narrows the union This is the standard pattern for handling overloaded parameters 3. Union in Implementation vs Overloads The implementation uses union type (string | number) But the signatures show the specific combinations the function accepts This difference is important! 4. Common Mistake ❌ Forgetting to add the function body (all overloads need an implementation) ❌ Making the implementation too specific (must use union of all cases) ✅ Using typeof or instanceof to narrow the type inside the function ================================================================================ REAL-WORLD EXAMPLES: ================================================================================ Example 1: Format Function function format(value: string): string; function format(value: number): string; function format(value: string | number): string { if (typeof value === "string") { return value.trim().toLowerCase(); } else { return value.toFixed(2); } } console.log(format(" HELLO ")); // "hello" console.log(format(3.14159)); // "3.14" Example 2: Length Function (polymorphic) function length(value: string): number; function length(value: any[]): number; function length(value: string | any[]): number { return value.length; } console.log(length("hello")); // 5 console.log(length([1, 2, 3])); // 3 Example 3: Get First Element function first(value: string): string; function first(value: any[]): any; function first(value: string | any[]): any { return value[0]; } console.log(first("abc")); // "a" console.log(first([10, 20, 30])); // 10 ================================================================================ TESTING & VERIFICATION: ================================================================================ // Comprehensive test suite const testCases = [ { input: "hi", expected: false }, { input: "hello!", expected: true }, { input: "javascript", expected: true }, { input: "a", expected: false }, { input: 0, expected: false }, { input: 100, expected: false }, { input: 101, expected: true }, { input: 999, expected: true }, { input: -50, expected: false }, { input: "", expected: false }, ]; console.log("Running tests...\n"); let passed = 0; let failed = 0; testCases.forEach((test, index) => { const result = isLarge(test.input as any); const status = result === test.expected ? "✅ PASS" : "❌ FAIL"; if (result === test.expected) { passed++; } else { failed++; } console.log(`Test ${index + 1}: ${status}`); console.log(` Input: ${JSON.stringify(test.input)}`); console.log(` Expected: ${test.expected}, Got: ${result}\n`); }); console.log(`\nResults: ${passed} passed, ${failed} failed`); Expected output: Test 1: ✅ PASS Input: "hi" Expected: false, Got: false Test 2: ✅ PASS Input: "hello!" Expected: true, Got: true ... (all should pass) Results: 10 passed, 0 failed ================================================================================ BEST PRACTICES: ================================================================================ ✅ DO: Use overloads when behavior differs by type ✅ DO: Keep overload signatures simple and clear ✅ DO: Use typeof guards for primitive types ✅ DO: Test with various input types ❌ DON'T: Create overloads just for the sake of it ❌ DON'T: Make the implementation more complex than needed ❌ DON'T: Forget to handle all union cases in the implementation ❌ DON'T: Use 'any' in the implementation (defeats the purpose) ================================================================================