SOLUTION: Challenge 3 - Recursive Generic =========================================== Challenge: Create a type Flatten that "unwraps" nested arrays. For example, Flatten<[1, [2, 3]]> should be 1 | 2 | 3. --- SOLUTION: // Recursive flatten: unwrap nested arrays type Flatten = T extends (infer U)[] ? Flatten : T; // Test it type Test1 = Flatten; // number type Test2 = Flatten; // string type Test3 = Flatten<(number | string)[]>; // number | string type Test4 = Flatten; // number type Test5 = Flatten<[1, [2, [3, 4]]]>; // 1 | 2 | 3 | 4 // Verify the types work as expected const test1: Test1 = 42; // ✅ number const test4: Test4 = 42; // ✅ number const test5: Test5 = 3; // ✅ 3 is in the union --- EXPLANATION: HOW IT WORKS: 1. T extends (infer U)[] — Does T look like an array? If yes, infer the element type as U and recursively flatten it. If no, return T as-is (it's a leaf value). 2. Recursive call — Flatten If U is also an array, keep unwrapping. If not, stop. 3. Base case — T (when not an array) When we hit a non-array value, that's our result. STEP-BY-STEP EXAMPLE for Flatten<[1, [2, 3]]>: Input: [1, [2, 3]] Is it an array? Yes → infer U = (1 | [2, 3]) Recursively call Flatten<1 | [2, 3]> Unions distribute through conditionals, so this becomes: Flatten<1> | Flatten<[2, 3]> Flatten<1>: Is 1 an array? No → return 1 Flatten<[2, 3]>: Is it an array? Yes → infer U = (2 | 3) Recursively call Flatten<2 | 3> = Flatten<2> | Flatten<3> = 2 | 3 Result: 1 | 2 | 3 ✅ --- ALTERNATIVE: Array-specific version If you only care about array literals (not general array types): type FlattenArray = T extends (infer U)[] ? U extends any[] ? FlattenArray : U : T; This is stricter but clearer about only accepting arrays. --- ADVANCED: Flatten to a max depth Sometimes you only want to flatten N levels deep: type FlattenN = D extends 0 ? T : T extends (infer U)[] ? FlattenN // Decrement depth : T; type OneLevelDeep = FlattenN; // number[][] type TwoLevelsDeep = FlattenN; // number[] type FullyFlat = FlattenN; // number (This uses a tuple trick to decrement the depth number.) --- REAL-WORLD USAGE: This pattern is used in TypeScript's standard library: // Built-in flat() method has a depth parameter declare const arr: number[][][]; const shallow = arr.flat(1); // number[][] const deep = arr.flat(Infinity); // number[] TypeScript's type definitions for Array.flat() use similar recursive logic to ensure the return type is correctly flattened to the right depth. --- WHY RECURSIVE GENERICS MATTER: 1. Handle nested/tree structures without losing type safety. 2. Recursively process configurations, DTOs, and nested data. 3. Build type transformations that work at any depth. 4. Express "repeat this logic until condition" in the type system. Mastering recursion in generics is the next level of TypeScript expertise.