Create TerminalStatus from Status using Exclude or Extract
This tests conditional type mechanics and whitelist versus blacklist thinking. Extract selects matches by assignability; Exclude removes others. Prefer Extract when the desired set is explicit. Red flag: claiming they are equivalent or only knowing Exclude.
WHAT THIS TESTS: This question evaluates your familiarity with TypeScript's built-in conditional utility types and whether you can reason about set operations on unions. The interviewer wants to see that you know Extract and Exclude are not synonyms but duals: one keeps types assignable to a pattern, the other removes them. At the senior level, they also care if you can articulate maintainability trade-offs, such as which approach survives better when the source union changes.
A GOOD ANSWER COVERS: First, write the implementation using Extract: type TerminalStatus = Extract<Status, 'success' | 'error'>. Second, explain that Extract<T, U> keeps members of T that are assignable to U, which is a whitelist. Third, show the Exclude alternative: type TerminalStatus = Exclude<Status, 'pending' | 'idle'>, noting that Exclude<T, U> removes members of T that are assignable to U, which is a blacklist. Fourth, justify the choice: prefer Extract when the terminal set is small and semantically stable, because adding a new non-terminal status to Status will not leak into TerminalStatus. Prefer Exclude when the unwanted set is smaller or volatile. Fifth, mention that both are implemented with conditional types under the hood, for example Extract is roughly T extends U ? T : never.
COMMON WRONG ANSWERS: Answering with only Exclude because you have never used Extract. Saying the two utilities are interchangeable; they produce the same result here but read differently to future maintainers. Proposing manual conditional types without acknowledging the built-ins. Suggesting runtime methods like filter or array operations instead of type-level transforms. Forgetting that the second argument to Extract or Exclude can itself be a union.
LIKELY FOLLOW-UPS: How would you derive TerminalStatus dynamically if the terminal criteria were more complex, such as all Status values except those ending in ing? What happens if Status is refactored into an enum or a const object with as const? Can you implement Extract or Exclude yourself using infer? How do these utilities interact with distributive conditional types when T is a union?
ONE CONCRETE EXAMPLE: Imagine a payment state machine with Status = 'pending' | 'processing' | 'success' | 'error' | 'refunded'. A dashboard widget only cares about final outcomes. Using Extract<Status, 'success' | 'error' | 'refunded'> makes the intent obvious: these are the terminal states. If the product later adds 'disputed', the widget's type remains correct without edits. If you had used Exclude<Status, 'pending' | 'processing'> instead, the new 'disputed' state would silently appear in TerminalStatus, which might be a bug if disputes need separate handling.
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.