Challenge 1: Design a Message Envelope — Possible Solution ==================================================================== type ClientMessage = | { type: "move"; playerId: string; x: number; y: number } | { type: "chat"; playerId: string; text: string } | { type: "leave"; playerId: string }; function handleMessage(message: ClientMessage): void { switch (message.type) { case "move": // Narrowed to the "move" variant — x/y exist, text doesn't. console.log(`Player ${message.playerId} moved to (${message.x}, ${message.y})`); break; case "chat": // Narrowed to "chat" — text exists, x/y don't. console.log(`Player ${message.playerId} says: ${message.text}`); break; case "leave": // Narrowed to "leave" — only playerId exists. console.log(`Player ${message.playerId} left the game`); break; } } // --- Usage --- handleMessage({ type: "move", playerId: "p1", x: 10, y: 20 }); handleMessage({ type: "chat", playerId: "p1", text: "hello!" }); handleMessage({ type: "leave", playerId: "p1" }); // A compile-time check that exhaustiveness would be enforced with a default case: function handleMessageExhaustive(message: ClientMessage): void { switch (message.type) { case "move": console.log(message.x, message.y); return; case "chat": console.log(message.text); return; case "leave": console.log(message.playerId); return; default: // If a new variant is ever added to ClientMessage and this switch // isn't updated, `message` here is not `never`, and TypeScript // flags the mismatch — this is the exhaustiveness check from // Course 1's Advanced Types chapter. const _exhaustive: never = message; return _exhaustive; } } WHY THIS WORKS -------------- - `type` is the discriminant field shared by all three variants but with a different literal value each time — TypeScript narrows the whole object inside each `case` branch based on that one field. - Accessing `message.x` inside the "chat" case (or `message.text` inside "move") would be a compile error — the compiler enforces that each branch only uses fields that variant actually has. - The exhaustive version demonstrates the same never-typed default-case pattern used for exhaustiveness checking in Course 1 — useful once a message protocol has more than a couple of variants and might grow.