How would you model a WebSocket message discriminated union in TypeScript?
This tests TypeScript discriminated union narrowing. Define interfaces with readonly literal kind fields, union them, and narrow with switch or if checks. A red flag is typing kind as generic string or using type assertions instead of narrowing.
WHAT THIS TESTS: This question evaluates whether you understand discriminated unions, also called tagged unions, in TypeScript. The interviewer wants to see that you know how to use a shared literal property to let the compiler narrow a broad union into a specific member automatically. It also surfaces whether you rely on structural typing and control flow analysis rather than manual casts or runtime guessing.
A GOOD ANSWER COVERS: A good answer hits four things in order. First, define separate interfaces for each message variant, such as ChatMessage, UserStatusUpdate, and ErrorNotification, and give each a readonly kind property whose type is a string literal like chat, status, or error rather than the general string type. Second, combine those interfaces into a single union type, for example type WebSocketMessage equals ChatMessage or UserStatusUpdate or ErrorNotification. Third, explain that checking the kind property with a switch statement or strict equality if blocks triggers TypeScript narrowing, so inside each branch the compiler knows the exact shape and allows access to variant-specific fields without type assertions. Fourth, mention exhaustiveness checking, such as a default branch that assigns the message to the never type, which forces a compile-time error if a new variant is added later.
COMMON WRONG ANSWERS: There are three common wrong answers to avoid. One is typing the kind property as plain string instead of a literal type, which makes every union member look identical to the compiler and completely breaks narrowing. Two is using type assertions to cast a value to ChatMessage after an if check, which bypasses the type system and creates a maintenance burden when shapes change. Three is modeling the message as a single interface with every possible field marked optional, which weakens the type model and allows invalid states that the compiler cannot catch.
LIKELY FOLLOW-UPS: The interviewer may ask how you enforce exhaustive handling at compile time, which leads to the never-based exhaustiveness check. They may also ask how you handle untrusted WebSocket payloads from the wire, which opens a conversation about runtime validation libraries like Zod or io-ts before the data enters the typed union. Another angle is deriving handler maps or event emitter types from the union using mapped types, testing deeper type manipulation skills.
ONE CONCRETE EXAMPLE: Consider three interfaces. ChatMessage has readonly kind set to the literal chat and payload as string. UserStatusUpdate has readonly kind set to status and online as boolean. ErrorNotification has readonly kind set to error and code as number. You define type WebSocketMessage as the union of these three. In a handleMessage function, you switch on the kind property. When the case is chat, TypeScript knows the payload property is a string. When the case is status, the online property is boolean. When the case is error, the code property is number. In the default branch, you assign message to a parameter of type never and throw, so adding a new message kind later produces a compile error until you add its case.
Read the original → typescriptlang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.