Lesson 15 of 80
An if/else statement that runs at the type level instead of the value level — and `infer` is how it captures a piece of a type to reuse, instead of just checking it.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 2
In `type ElementType<T> = T extends (infer U)[] ? U : never;`, what does `infer U` actually do, beyond checking that T is an array?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<"hello">; // "yes"
type B = IsString<42>; // "no"
T extends U ? X : Y reads exactly like a ternary, but entirely in type-space: "if T is assignable to U, resolve to type X, otherwise resolve to type Y." This is how generic utility types make real decisions based on what type they were given, instead of behaving identically for every input.
infer: capturing a piece of the type you're checkingtype ElementType<T> = T extends (infer U)[] ? U : never;
type A = ElementType<string[]>; // string
type B = ElementType<number>; // never — number doesn't extend an array at all
infer U inside the extends clause doesn't just check "is this an array" — it captures whatever the element type turns out to be, and makes it available as U on the true branch. This is exactly how TypeScript's built-in ReturnType<F> works: T extends (...args: any[]) => infer R ? R : never — check that it's a function, then capture and return whatever its return type is.
type IsString<T> = T extends string ? true : false; — a type-level if/else.
IsString<"a" | 42> doesn't check the whole union at once — it applies the check to each member separately and unions the results, unless you deliberately wrap the type in [T] to opt out of that behavior.ReturnType, Parameters, Awaited, Exclude, Extract are all conditional types with infer, not special compiler primitives.never in the false branch is a common, deliberate pattern — "if this doesn't match the shape I'm checking for, produce never," which then naturally filters out of a union (since a union with never in it just drops that member).Implementing a simplified version of ReturnType<F> or Awaited<T> is exactly the kind of advanced-TypeScript exercise senior interviews use — it directly checks whether conditional types and infer are things you actually understand, not just utility types you've memorized the names of.