Challenge 2: Write a Type-Safe zip — Possible Solution ==================================================================== type Zip = T extends [infer THead, ...infer TTail] ? U extends [infer UHead, ...infer UTail] ? [[THead, UHead], ...Zip] : [] : []; function zip(a: [...T], b: [...U]): Zip { const length = Math.min(a.length, b.length); const result: unknown[] = []; for (let i = 0; i < length; i++) { result.push([a[i], b[i]]); } return result as Zip; } // --- Usage --- const zipped = zip([1, 2] as [number, number], ["a", "b"] as [string, string]); // zipped: [[number, string], [number, string]] console.log(zipped[0][0], zipped[0][1]); // 1, "a" — typed as number, string respectively const mixed = zip( [1, "x", true] as [number, string, boolean], ["a", 2] as [string, number] ); // mixed: [[number, string], [string, number]] // The third element of the first tuple has no partner in the second tuple, // so Zip stops there — matching the runtime behavior (shorter array wins). WHY THIS WORKS -------------- - Zip recurses on BOTH tuples simultaneously: each step peels the head off T and the head off U, pairs them into a two-element tuple `[THead, UHead]`, and prepends that pair onto the recursive result for the remaining tails — the same [First, ...Rest] destructuring pattern from Head/Tail, just applied to two tuples in lockstep. - The recursion's base case triggers the moment EITHER tuple runs out of elements (either `T extends [infer THead, ...]` or the nested `U extends [infer UHead, ...]` fails to match), producing `[]` — which correctly mirrors how a runtime zip stops at the shorter of the two inputs. - The `TTail extends unknown[] ? TTail : []` guards exist because TypeScript's inferred `infer TTail` doesn't automatically know it's constrained to `unknown[]` for the recursive call — this satisfies the generic constraint `T extends unknown[]` on the next recursive Zip call.