How do you type a function with two possible response shapes?
This checks TypeScript union types for API responses. Define a union of Product[] and a message object, use a type guard to narrow it at runtime, and return that.
WHAT THIS TESTS: This question tests whether you understand how to use TypeScript union types to accurately model real-world API contracts where a single endpoint can return structurally different payloads. The interviewer wants to see that you reject the temptation to use loose types like any or overly restrictive types like Product[] alone, and instead compose a precise union that preserves type safety from the network boundary through to the consumer.
A GOOD ANSWER COVERS: First, define explicit types for each shape, such as type Product equals an object with id and name strings, and type EmptyResponse equals an object with a message string. Second, combine them into a union type like type SearchResponse equals Product[] or EmptyResponse. Third, use a type guard at runtime to disambiguate the two branches, for example by checking if Array.isArray(response) or if message is a property of response, so TypeScript can narrow the union inside each branch. Fourth, return or expose the narrowed result so calling code gets exact types and autocomplete rather than a vague union.
COMMON WRONG ANSWERS: The biggest red flag is declaring the return type as any or unknown and moving on, which defeats the purpose of TypeScript. Another frequent mistake is typing the response as only Product[] and then forcing consumers to handle the empty-state object with defensive checks or type assertions, which hides bugs until runtime. A subtler error is creating a union but failing to narrow it, leaving every consumer to deal with a type that could be either shape without any guidance from the compiler.
LIKELY FOLLOW-UPS: The interviewer might ask how you would handle this if the API returned a 200 status for both shapes versus using a 404 status code for the empty state. They could also ask how to make the union more robust with a discriminant property like a success object with a data array or an empty object with a message string, or how you would validate the shape at runtime using a library like Zod when the API is outside your control.
ONE CONCRETE EXAMPLE: You might write type SearchResponse equals Product[] or an object with message string, then implement an async function fetchProducts that takes a query string and returns a Promise of SearchResponse. Inside, you await a fetch call to the search endpoint, parse the JSON, and then check if Array.isArray(data). If it is an array, you return the product array; otherwise, you return the message object. In the calling code, you await fetchProducts with a query like shoes, then branch with if Array.isArray(result) to call map on the products, else log the message. This pattern keeps the type system honest and prevents calling map on a message object or reading message on an array.
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.