Challenge 1: Design the Response Envelope — Possible Solution ==================================================================== type ApiResponse = | { status: "success"; data: T } | { status: "error"; message: string; code: number }; function handleResponse(response: ApiResponse): void { if (response.status === "success") { // TypeScript narrows to the success branch: `data` exists, `message`/`code` don't. console.log("Got data:", response.data); } else { // Narrowed to the error branch: `message` and `code` exist, `data` doesn't. console.error(`Error ${response.code}: ${response.message}`); } } // --- Usage --- interface Product { id: string; name: string; price: number; } const successResponse: ApiResponse = { status: "success", data: { id: "p1", name: "Widget", price: 9.99 }, }; const errorResponse: ApiResponse = { status: "error", message: "Product not found", code: 404, }; handleResponse(successResponse); // "Got data: { id: 'p1', ... }" handleResponse(errorResponse); // "Error 404: Product not found" // A compile-time check that narrowing actually works: function getPrice(response: ApiResponse): number { if (response.status === "success") { return response.data.price; // ✅ compiles — data is Product here } // return response.data.price; // ❌ would NOT compile here — data doesn't exist on the error branch return 0; } WHY THIS WORKS -------------- - `status` is the discriminant: because both branches of the union share a literal-typed `status` field with different values ("success" vs "error"), TypeScript can narrow the whole object based on a single `if` check. - Generic `` means the same envelope works for any resource type — Product, User, Order — without writing a new response type for each one. - Attempting to access `data` in the error branch (or `message`/`code` in the success branch) is a compile error, not just a runtime risk — the type system enforces the contract.